Skip to content

Instantly share code, notes, and snippets.

@CyberShadow
Last active May 29, 2024 20:20

Revisions

  1. CyberShadow revised this gist May 29, 2024. 1 changed file with 46 additions and 2 deletions.
    48 changes: 46 additions & 2 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -53,7 +53,9 @@ void buildThing(ref SaveGame game, Config config)
    enum numInputIngesters = 5;
    enum inputIngesterSize = hubLength;
    enum inputPortLinkSize = numInputIngesters * inputIngesterSize;
    enum outputPortLinkSize = 0;
    enum numAvalancheCells = 0; // Optional "avalanche" feature for getting throughput high scores
    enum avalancheCellSize = 0.75;
    enum outputPortLinkSize = numAvalancheCells * avalancheCellSize;
    enum portLaneSize = 0.25;
    enum minPortFilterSize = 4;

    @@ -420,7 +422,9 @@ void buildThing(ref SaveGame game, Config config)
    addr: Filter.Config.Addr.Src,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Differ,
    collision: Filter.Config.Collision.SendBackOutbound,
    collision: numAvalancheCells == 0
    ? Filter.Config.Collision.SendBackOutbound
    : Filter.Config.Collision.DropInbound,
    ),
    );
    auto filterOutputHub = addHub(
    @@ -527,6 +531,46 @@ void buildThing(ref SaveGame game, Config config)
    inputIngesters[host] = NodePort(loopFilter, 0);
    }

    // Avalanche cells
    if (numAvalancheCells)
    foreach (HostIndex host; 0 .. numHosts)
    {
    auto port = cellPorts[Side.target][host];
    foreach (i; 0 .. numAvalancheCells)
    {
    auto filter = addFilter(
    pos: [
    startWorld[0] + terminatorSize + numHosts * cellSize + i * avalancheCellSize + avalancheCellSize * 0.5,
    objectY,
    startWorld[2] + terminatorSize + host * cellSize + cellSize * 1/8,
    ],
    angle: 0,
    config: i + 1 == numAvalancheCells
    ? Filter.Config(
    // The "go" switch
    port: 0,
    mask: wildcardAddress(Address.Type.UnrestrictedFilter),
    addr: Filter.Config.Addr.Dst,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Match,
    collision: Filter.Config.Collision.DropInbound,
    )
    : Filter.Config(
    // Avalanche cell - trap the packet
    port: 1,
    mask: wildcardAddress(Address.Type.UnrestrictedFilter),
    addr: Filter.Config.Addr.Dst,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Match,
    collision: Filter.Config.Collision.SendBackOutbound,
    ),
    );
    addRelays(port, NodePort(filter, 0));
    port = NodePort(filter, 1);
    }
    cellPorts[Side.target][host] = port;
    }

    // Input / output / bus integration
    foreach (HostIndex host; 0 .. numHosts)
    {
  2. CyberShadow revised this gist May 29, 2024. 1 changed file with 2 additions and 7 deletions.
    9 changes: 2 additions & 7 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -53,7 +53,7 @@ void buildThing(ref SaveGame game, Config config)
    enum numInputIngesters = 5;
    enum inputIngesterSize = hubLength;
    enum inputPortLinkSize = numInputIngesters * inputIngesterSize;
    enum outputPortLinkSize = cellSize;
    enum outputPortLinkSize = 0;
    enum portLaneSize = 0.25;
    enum minPortFilterSize = 4;

    @@ -552,10 +552,6 @@ void buildThing(ref SaveGame game, Config config)
    collision: Filter.Config.Collision.DropInbound,
    ),
    );
    auto outputRelay = addRelay(
    pos: [outputX0 + outputPortLinkSize * 0.5, objectY, outputZ0 + cellSize * 1/8],
    angle: 0,
    );
    auto outputToCornerRelay = addRelay(
    pos: [outputX0 + outputPortLinkSize + host * portLaneSize, objectY, outputZ0 + cellSize * 1/8],
    angle: 0,
    @@ -569,8 +565,7 @@ void buildThing(ref SaveGame game, Config config)

    addRelays(NodePort(inputOutputSplitHub, 1), inputIngesters[host]);

    addRelays(cellPorts[Side.target][host], NodePort(outputRelay, 0));
    addRelays(NodePort(outputRelay, 1), NodePort(outputToCornerRelay, 0));
    addRelays(cellPorts[Side.target][host], NodePort(outputToCornerRelay, 0));
    addRelays(NodePort(outputToCornerRelay, 1), NodePort(cornerRelay, 0));
    addRelays(NodePort(cornerRelay, 1), NodePort(inputOutputSplitHub, 2));

  3. CyberShadow revised this gist May 29, 2024. 1 changed file with 24 additions and 16 deletions.
    40 changes: 24 additions & 16 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -52,7 +52,8 @@ void buildThing(ref SaveGame game, Config config)
    enum cellSize = 2.0;
    enum numInputIngesters = 5;
    enum inputIngesterSize = hubLength;
    enum portLinkSize = numInputIngesters * inputIngesterSize;
    enum inputPortLinkSize = numInputIngesters * inputIngesterSize;
    enum outputPortLinkSize = cellSize;
    enum portLaneSize = 0.25;
    enum minPortFilterSize = 4;

    @@ -62,14 +63,21 @@ void buildThing(ref SaveGame game, Config config)

    enum ChunkCoord startChunk = {x: 11, y: 0, z: -2};

    auto totalSize =
    auto totalWorldXSize =
    terminatorSize +
    cellSize * numHosts +
    portLinkSize +
    outputPortLinkSize +
    portLaneSize * numHosts;
    auto totalChunkXSize = cast(int)ceil(totalWorldXSize / chunkSize);

    auto totalWorldZSize =
    terminatorSize +
    cellSize * numHosts +
    inputPortLinkSize +
    portLaneSize * numHosts +
    minPortFilterSize;
    auto totalChunks = cast(int)ceil(totalSize / chunkSize);
    totalSize = totalChunks * chunkSize;
    auto totalChunkZSize = cast(int)ceil(totalWorldZSize / chunkSize);
    totalWorldZSize = totalChunkZSize * chunkSize;

    auto startWorld = startChunk.toWorld;
    auto objectY = startWorld[1] + 5.5; // We will place all objects at this height.
    @@ -88,14 +96,14 @@ void buildThing(ref SaveGame game, Config config)
    struct ChunkPlan { ChunkGeometry geometry; bool firstRunOnly; }
    ChunkPlan[ChunkCoord] chunkPlan;
    // Switch
    foreach (z; startChunk.z .. startChunk.z + totalChunks)
    foreach (x; startChunk.x .. startChunk.x + totalChunks)
    foreach (z; startChunk.z .. startChunk.z + totalChunkZSize)
    foreach (x; startChunk.x .. startChunk.x + totalChunkXSize)
    chunkPlan[ChunkCoord(x, startChunk.y, z)] = ChunkPlan(ChunkGeometry.flat, false);
    foreach (x; startChunk.x .. startChunk.x + totalChunks)
    chunkPlan[ChunkCoord(x, startChunk.y, startChunk.z + totalChunks)] = ChunkPlan(ChunkGeometry.flat, true);
    foreach (x; startChunk.x .. startChunk.x + totalChunkXSize)
    chunkPlan[ChunkCoord(x, startChunk.y, startChunk.z + totalChunkZSize)] = ChunkPlan(ChunkGeometry.flat, true);
    // Bus
    auto busChunkZ = startChunk.z + totalChunks + 1;
    foreach (x; startChunk.x .. startChunk.x + totalChunks)
    auto busChunkZ = startChunk.z + totalChunkZSize + 1;
    foreach (x; startChunk.x .. startChunk.x + totalChunkXSize)
    chunkPlan[ChunkCoord(x, startChunk.y, busChunkZ)] = ChunkPlan(ChunkGeometry.flat, false);
    foreach (i, target; busTargets)
    {
    @@ -528,11 +536,11 @@ void buildThing(ref SaveGame game, Config config)
    auto outputZ0 = startWorld[2] + terminatorSize + host * cellSize;

    auto inputOutputSplitHub = addHub(
    pos: [inputX0 + cellSize * 1/8, objectY, inputZ0 + portLinkSize + host * portLaneSize],
    pos: [inputX0 + cellSize * 1/8, objectY, inputZ0 + inputPortLinkSize + host * portLaneSize],
    angle: 0,
    );
    auto inputFilter = addFilter(
    pos: [inputX0 + cellSize * 1/8, objectY, startWorld[2] + totalSize - 0.3],
    pos: [inputX0 + cellSize * 1/8, objectY, startWorld[2] + totalWorldZSize - 0.3],
    angle: 0,
    config: Filter.Config(
    // Sends back packets not from the intended peer. Mainly for identification.
    @@ -545,15 +553,15 @@ void buildThing(ref SaveGame game, Config config)
    ),
    );
    auto outputRelay = addRelay(
    pos: [outputX0 + portLinkSize * 0.5, objectY, outputZ0 + cellSize * 1/8],
    pos: [outputX0 + outputPortLinkSize * 0.5, objectY, outputZ0 + cellSize * 1/8],
    angle: 0,
    );
    auto outputToCornerRelay = addRelay(
    pos: [outputX0 + portLinkSize + host * portLaneSize, objectY, outputZ0 + cellSize * 1/8],
    pos: [outputX0 + outputPortLinkSize + host * portLaneSize, objectY, outputZ0 + cellSize * 1/8],
    angle: 0,
    );
    auto cornerRelay = addRelay(
    pos: [outputX0 + portLinkSize + host * portLaneSize, objectY, inputZ0 + portLinkSize + host * portLaneSize],
    pos: [outputX0 + outputPortLinkSize + host * portLaneSize, objectY, inputZ0 + inputPortLinkSize + host * portLaneSize],
    angle: 0,
    );

  4. CyberShadow revised this gist May 29, 2024. 1 changed file with 14 additions and 6 deletions.
    20 changes: 14 additions & 6 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -625,15 +625,15 @@ void buildThing(ref SaveGame game, Config config)
    stderr.writef("Dangle");
    else
    {
    auto peerNodeIndex = peer.get()[0];
    auto peerNodeIndex = peer.get().port[0];
    auto obj = game.getGameObject(peerNodeIndex);
    if (obj.isNull())
    stderr.writef("Unknown");
    else
    obj.get.match!(
    (ref const Endpoint endpoint) {
    if (endpoint.address == host.toAddress(Address.Type.Endpoint))
    stderr.writef("OK");
    stderr.writef("OK (%d)", peer.get().numHops);
    else
    stderr.writef(endpoint.address.elements.toString());
    },
    @@ -679,25 +679,33 @@ Nullable!NodePort getNodePortPeer(ref const SaveGame game, NodePort port)
    return typeof(return)();
    }

    Nullable!NodePort getFinalNodePortPeer(ref const SaveGame game, NodePort port)
    struct FinalPeer
    {
    NodePort port;
    size_t numHops;
    }

    Nullable!FinalPeer getFinalNodePortPeer(ref const SaveGame game, NodePort port)
    {
    size_t numHops;
    while (true)
    {
    auto peer = game.getNodePortPeer(port);
    if (peer.isNull)
    return peer;
    return typeof(return)();
    auto peerNode = peer.get()[0];
    auto peerPort = peer.get()[1];
    auto obj = game.getGameObject(peerNode);
    if (obj.isNull)
    return peer;
    return typeof(return)(FinalPeer(peer.get(), numHops));
    bool done;
    obj.get().match!(
    (ref const Relay relay) { done = false; port = NodePort(peerNode, cast(PortIndex)(1 - peerPort)); },
    (_ ) { done = true; },
    );
    if (done)
    return peer;
    return typeof(return)(FinalPeer(peer.get(), numHops));
    numHops++;
    }
    }

  5. CyberShadow revised this gist May 29, 2024. 1 changed file with 49 additions and 38 deletions.
    87 changes: 49 additions & 38 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -50,9 +50,9 @@ void buildThing(ref SaveGame game, Config config)
    enum numHosts = 4 ^^ 3;
    enum terminatorSize = 4.0;
    enum cellSize = 2.0;
    enum numInputIngesters = 3;
    enum numInputIngesters = 5;
    enum inputIngesterSize = hubLength;
    enum portLinkSize = numInputIngesters * inputIngesterSize + 0.5;
    enum portLinkSize = numInputIngesters * inputIngesterSize;
    enum portLaneSize = 0.25;
    enum minPortFilterSize = 4;

    @@ -441,23 +441,15 @@ void buildThing(ref SaveGame game, Config config)
    auto inputX0 = startWorld[0] + terminatorSize + host * cellSize;
    auto inputZ0 = startWorld[2] + terminatorSize + numHosts * cellSize;

    NodePort lastInnerPort = cellPorts[Side.source][host];
    NodePort lastOuterPort; // will be created on the first iteration
    Nullable!NodePort lastInnerPort = cellPorts[Side.source][host];
    Nullable!NodePort lastOuterPort; // will be created on the first iteration

    assert(numInputIngesters > 0); // Must have at least one. One ingester is just one filter, no hubs.

    foreach (i; 0 .. numInputIngesters)
    {
    auto z = inputZ0 + inputIngesterSize * i + inputIngesterSize * 0.5;

    auto innerHub = i + 1 != numInputIngesters
    ? addHub(
    pos: [inputX0 + cellSize * 1/8, objectY, z],
    angle: -PI/2,
    )
    : addRelay(
    pos: [inputX0 + cellSize * 1/8, objectY, z],
    );
    auto filter = addFilter(
    pos: [inputX0 + cellSize * 1/8 + 0.65, objectY, z],
    angle: -PI/2,
    @@ -467,45 +459,64 @@ void buildThing(ref SaveGame game, Config config)
    addr: Filter.Config.Addr.Dst,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Match,
    collision: i == 0
    // Sends back all packets. On collision, drop the inbound (old) packet.
    ? Filter.Config.Collision.DropInbound
    // The additional filters will buffer.
    : Filter.Config.Collision.SendBackOutbound,
    collision: Filter.Config.Collision.SendBackOutbound,
    ),
    );
    auto outerHub = i != 0
    ? addHub(
    pos: [inputX0 + cellSize * 1/8 + 1.3, objectY, z],
    angle: -PI/2,
    )
    : addRelay(
    pos: [inputX0 + cellSize * 1/8 + 1.3, objectY, z],
    );

    if (i != 0)
    addEdge(lastOuterPort, NodePort(outerHub, 2));
    addEdge(NodePort(outerHub, 1), NodePort(filter, 0));
    lastOuterPort = NodePort(outerHub, 0);

    addEdge(lastInnerPort, NodePort(innerHub, 0));
    addEdge(NodePort(innerHub, 1), NodePort(filter, 1));
    if (i + 1 == numInputIngesters)
    lastInnerPort = NodePort.init;
    {
    // Wire directly to filter
    addEdge(lastInnerPort.get(), NodePort(filter, 1));
    lastInnerPort = Nullable!NodePort();
    }
    else
    {
    auto innerHub = addHub(
    pos: [inputX0 + cellSize * 1/8, objectY, z],
    angle: -PI/2,
    );
    addEdge(lastInnerPort.get(), NodePort(innerHub, 0));
    addEdge(NodePort(innerHub, 1), NodePort(filter, 1));
    lastInnerPort = NodePort(innerHub, 2);
    }

    if (i == 0)
    {
    // Wire directly from filter
    lastOuterPort = NodePort(filter, 0);
    }
    else
    {
    auto outerHub = addHub(
    pos: [inputX0 + cellSize * 1/8 + 1.3, objectY, z],
    angle: -PI/2,
    );
    addEdge(lastOuterPort.get(), NodePort(outerHub, 2));
    addEdge(NodePort(outerHub, 1), NodePort(filter, 0));
    lastOuterPort = NodePort(outerHub, 0);
    }
    }

    // One final relay to allow things to line up
    auto finalRelay = addRelay(
    // One final filter to 1) allow things to line up 2) trap packets to keep retrying
    auto loopFilter = addFilter(
    pos: [
    // The position where the last hub would be
    inputX0 + cellSize * 1/8,
    objectY,
    inputZ0 + inputIngesterSize * numInputIngesters + 0.25
    inputZ0 + inputIngesterSize * (numInputIngesters - 1) + inputIngesterSize * 0.5
    ],
    angle: 0,
    config: Filter.Config(
    port: 1,
    mask: wildcardAddress(Address.Type.UnrestrictedFilter),
    addr: Filter.Config.Addr.Dst,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Match,
    collision: Filter.Config.Collision.DropInbound,
    ),
    );
    addEdge(lastOuterPort, NodePort(finalRelay, 1));
    inputIngesters[host] = NodePort(finalRelay, 0);
    addEdge(lastOuterPort.get(), NodePort(loopFilter, 1));
    inputIngesters[host] = NodePort(loopFilter, 0);
    }

    // Input / output / bus integration
  6. CyberShadow revised this gist May 29, 2024. 2 changed files with 92 additions and 19 deletions.
    104 changes: 85 additions & 19 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -50,7 +50,9 @@ void buildThing(ref SaveGame game, Config config)
    enum numHosts = 4 ^^ 3;
    enum terminatorSize = 4.0;
    enum cellSize = 2.0;
    enum portLinkSize = cellSize;
    enum numInputIngesters = 3;
    enum inputIngesterSize = hubLength;
    enum portLinkSize = numInputIngesters * inputIngesterSize + 0.5;
    enum portLaneSize = 0.25;
    enum minPortFilterSize = 4;

    @@ -430,25 +432,90 @@ void buildThing(ref SaveGame game, Config config)
    }
    }

    // Input ingesters
    // (This mechanism attempts to solve the problem of packet drops due to unfortunate timing.
    // It works by giving packets more than one attempt to enter the input loop.)
    NodePort[numHosts] inputIngesters;
    foreach (HostIndex host; 0 .. numHosts)
    {
    auto inputX0 = startWorld[0] + terminatorSize + host * cellSize;
    auto inputZ0 = startWorld[2] + terminatorSize + numHosts * cellSize;

    NodePort lastInnerPort = cellPorts[Side.source][host];
    NodePort lastOuterPort; // will be created on the first iteration

    assert(numInputIngesters > 0); // Must have at least one. One ingester is just one filter, no hubs.

    foreach (i; 0 .. numInputIngesters)
    {
    auto z = inputZ0 + inputIngesterSize * i + inputIngesterSize * 0.5;

    auto innerHub = i + 1 != numInputIngesters
    ? addHub(
    pos: [inputX0 + cellSize * 1/8, objectY, z],
    angle: -PI/2,
    )
    : addRelay(
    pos: [inputX0 + cellSize * 1/8, objectY, z],
    );
    auto filter = addFilter(
    pos: [inputX0 + cellSize * 1/8 + 0.65, objectY, z],
    angle: -PI/2,
    config: Filter.Config(
    port: 1,
    mask: wildcardAddress(Address.Type.UnrestrictedFilter),
    addr: Filter.Config.Addr.Dst,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Match,
    collision: i == 0
    // Sends back all packets. On collision, drop the inbound (old) packet.
    ? Filter.Config.Collision.DropInbound
    // The additional filters will buffer.
    : Filter.Config.Collision.SendBackOutbound,
    ),
    );
    auto outerHub = i != 0
    ? addHub(
    pos: [inputX0 + cellSize * 1/8 + 1.3, objectY, z],
    angle: -PI/2,
    )
    : addRelay(
    pos: [inputX0 + cellSize * 1/8 + 1.3, objectY, z],
    );

    if (i != 0)
    addEdge(lastOuterPort, NodePort(outerHub, 2));
    addEdge(NodePort(outerHub, 1), NodePort(filter, 0));
    lastOuterPort = NodePort(outerHub, 0);

    addEdge(lastInnerPort, NodePort(innerHub, 0));
    addEdge(NodePort(innerHub, 1), NodePort(filter, 1));
    if (i + 1 == numInputIngesters)
    lastInnerPort = NodePort.init;
    else
    lastInnerPort = NodePort(innerHub, 2);
    }

    // One final relay to allow things to line up
    auto finalRelay = addRelay(
    pos: [
    inputX0 + cellSize * 1/8,
    objectY,
    inputZ0 + inputIngesterSize * numInputIngesters + 0.25
    ],
    );
    addEdge(lastOuterPort, NodePort(finalRelay, 1));
    inputIngesters[host] = NodePort(finalRelay, 0);
    }

    // Input / output / bus integration
    foreach (HostIndex host; 0 .. numHosts)
    {
    auto inputX0 = startWorld[0] + terminatorSize + host * cellSize;
    auto inputZ0 = startWorld[2] + terminatorSize + numHosts * cellSize;
    auto outputX0 = startWorld[0] + terminatorSize + numHosts * cellSize;
    auto outputZ0 = startWorld[2] + terminatorSize + host * cellSize;
    auto inputLoopFilter = addFilter(
    pos: [inputX0 + cellSize * 1/8, objectY, inputZ0 + cellSize * 0.5],
    angle: 0,
    config: Filter.Config(
    // Sends back all packets. On collision, drop the inbound (old) packet.
    port: 1,
    mask: wildcardAddress(Address.Type.UnrestrictedFilter),
    addr: Filter.Config.Addr.Dst,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Match,
    collision: Filter.Config.Collision.DropInbound,
    ),
    );

    auto inputOutputSplitHub = addHub(
    pos: [inputX0 + cellSize * 1/8, objectY, inputZ0 + portLinkSize + host * portLaneSize],
    angle: 0,
    @@ -480,15 +547,14 @@ void buildThing(ref SaveGame game, Config config)
    );

    addRelays(NodePort(inputFilter, 1), NodePort(inputOutputSplitHub, 0));
    addRelays(NodePort(inputOutputSplitHub, 1), NodePort(inputLoopFilter, 0));

    addRelays(NodePort(inputOutputSplitHub, 1), inputIngesters[host]);

    addRelays(cellPorts[Side.target][host], NodePort(outputRelay, 0));
    addRelays(NodePort(outputRelay, 1), NodePort(outputToCornerRelay, 0));
    addRelays(NodePort(outputToCornerRelay, 1), NodePort(cornerRelay, 0));
    addRelays(NodePort(cornerRelay, 1), NodePort(inputOutputSplitHub, 2));

    addRelays(cellPorts[Side.source][host], NodePort(inputLoopFilter, 1));
    addRelays(cellPorts[Side.target][host], NodePort(outputRelay, 0));

    peerPorts[host] = NodePort(inputFilter, 0);
    }

    @@ -543,7 +609,7 @@ void buildThing(ref SaveGame game, Config config)
    foreach (port; [peerPorts[host], busOuterPorts[host]])
    {
    stderr.write("\t");
    auto peer = game.getFinalNodePortPeer(peerPorts[host]);
    auto peer = game.getFinalNodePortPeer(port);
    if (peer.isNull())
    stderr.writef("Dangle");
    else
    7 changes: 7 additions & 0 deletions game.d
    Original file line number Diff line number Diff line change
    @@ -250,6 +250,13 @@ ChunkCoord toChunk(WorldCoord c)

    ///////////////////////////////////////////////////////////////////////////////

    enum relaySize = 0.4;
    enum hubLength = 1.0;
    enum hubWidth = 0.5;
    enum filterSize = 0.6;

    ///////////////////////////////////////////////////////////////////////////////

    Address.Element[4] parseAddress(string str)
    {
    Address.Element[4] result;
  7. CyberShadow revised this gist May 28, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -222,7 +222,7 @@ void buildThing(ref SaveGame game, Config config)
    return index;
    }

    NodeIndex addRelay(WorldCoord pos, WorldCoord up = upVector, double angle = 0, bool fixed = false, bool light = false)
    NodeIndex addRelay(WorldCoord pos, WorldCoord up = upVector, double angle = 0, bool fixed = false, bool light = true)
    {
    auto index = addNode(pos, up, angle);
    game.relays ~= Relay(index, fixed: fixed, light: light);
    @@ -280,7 +280,7 @@ void buildThing(ref SaveGame game, Config config)
    vec[] /= vecLength;
    auto relayCoord = sourceCoord;
    relayCoord[] += vec[] * maxWireLength;
    auto relay = addRelay(relayCoord);
    auto relay = addRelay(relayCoord, light: false);
    addEdge(source, NodePort(relay, 0), color);

    source = NodePort(relay, 1);
  8. CyberShadow revised this gist May 28, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -406,10 +406,10 @@ void buildThing(ref SaveGame game, Config config)
    // collision: Filter.Config.Collision.SendBackOutbound,

    // This variant (which should always match) allows identification of the source:
    mask: toAddress(sourceHost, Address.Type.UnrestrictedFilter),
    mask: Address((){ auto a = sourceHost.toAddressElements(); a[0] = Address.Element.Three; return a; }(), Address.Type.UnrestrictedFilter),
    addr: Filter.Config.Addr.Src,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Match,
    op: Filter.Config.Op.Differ,
    collision: Filter.Config.Collision.SendBackOutbound,
    ),
    );
  9. CyberShadow revised this gist May 28, 2024. 2 changed files with 105 additions and 4 deletions.
    102 changes: 99 additions & 3 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -10,9 +10,12 @@ import std.format;
    import std.math.algebraic;
    import std.math.constants;
    import std.math.rounding;
    import std.meta : AliasSeq;
    import std.path;
    import std.range;
    import std.stdio : stderr;
    import std.sumtype;
    import std.traits;
    import std.typecons;

    import ae.sys.file;
    @@ -304,6 +307,9 @@ void buildThing(ref SaveGame game, Config config)
    assert(numAddedEdges <= 1, "Ambiguous reconnect");
    }

    // Switch edge port to bus / endpoint
    NodePort[numHosts] peerPorts;

    // Create the switch
    {
    enum Side
    @@ -424,9 +430,6 @@ void buildThing(ref SaveGame game, Config config)
    }
    }

    // Switch edge port to endpoint
    NodePort[numHosts] peerPorts;

    foreach (HostIndex host; 0 .. numHosts)
    {
    auto inputX0 = startWorld[0] + terminatorSize + host * cellSize;
    @@ -496,6 +499,7 @@ void buildThing(ref SaveGame game, Config config)

    // The bus

    NodePort[numHosts] busInnerPorts, busOuterPorts;
    foreach (HostIndex host; 0 .. numHosts)
    {
    enum hostsPerBusArm = 16;
    @@ -525,6 +529,98 @@ void buildThing(ref SaveGame game, Config config)
    addRelays(NodePort(busStart, 1), NodePort(busFinish, 0));
    addRelays(NodePort(busFinish, 1), NodePort(busExit, 0));
    reconnect(NodePort(busExit, 1));

    busOuterPorts[host] = NodePort(busEnter, 0);
    busInnerPorts[host] = NodePort(busExit, 1);
    }

    // Check connectivity

    stderr.writefln("Host\tSwitch\tBus");
    foreach (HostIndex host; 0 .. numHosts)
    {
    stderr.writef(host.toAddressElements().toString());
    foreach (port; [peerPorts[host], busOuterPorts[host]])
    {
    stderr.write("\t");
    auto peer = game.getFinalNodePortPeer(peerPorts[host]);
    if (peer.isNull())
    stderr.writef("Dangle");
    else
    {
    auto peerNodeIndex = peer.get()[0];
    auto obj = game.getGameObject(peerNodeIndex);
    if (obj.isNull())
    stderr.writef("Unknown");
    else
    obj.get.match!(
    (ref const Endpoint endpoint) {
    if (endpoint.address == host.toAddress(Address.Type.Endpoint))
    stderr.writef("OK");
    else
    stderr.writef(endpoint.address.elements.toString());
    },
    (ref const other) {
    stderr.writef(Unqual!(typeof(other)).stringof);
    }
    );
    }
    }
    stderr.writeln();
    }
    }

    alias GameObject = SumType!(
    Endpoint,
    Relay,
    Filter,
    Hub,
    Bridge,
    );
    Nullable!GameObject getGameObject(ref const SaveGame game, NodeIndex node)
    {
    static foreach (getObjectsOfType; AliasSeq!(
    () => game.endpoints,
    () => game.relays,
    () => game.filters,
    () => game.hubs,
    () => game.bridges,
    ))
    foreach (obj; getObjectsOfType())
    if (obj.node == node)
    return typeof(return)(GameObject(obj));
    return typeof(return)();
    }

    Nullable!NodePort getNodePortPeer(ref const SaveGame game, NodePort port)
    {
    foreach (edge; game.edges)
    if (edge[0] == port)
    return typeof(return)(edge[1]);
    else if (edge[1] == port)
    return typeof(return)(edge[0]);
    return typeof(return)();
    }

    Nullable!NodePort getFinalNodePortPeer(ref const SaveGame game, NodePort port)
    {
    while (true)
    {
    auto peer = game.getNodePortPeer(port);
    if (peer.isNull)
    return peer;
    auto peerNode = peer.get()[0];
    auto peerPort = peer.get()[1];
    auto obj = game.getGameObject(peerNode);
    if (obj.isNull)
    return peer;
    bool done;
    obj.get().match!(
    (ref const Relay relay) { done = false; port = NodePort(peerNode, cast(PortIndex)(1 - peerPort)); },
    (_ ) { done = true; },
    );
    if (done)
    return peer;
    }
    }

    7 changes: 6 additions & 1 deletion game.d
    Original file line number Diff line number Diff line change
    @@ -121,6 +121,11 @@ struct Hub
    bool dir;
    }

    struct Bridge
    {
    NodeIndex node;
    }

    struct ChunkCoord { int x, y, z; }
    alias ChunkRunLength = ushort;
    enum Material : ubyte
    @@ -177,7 +182,7 @@ struct SaveGame
    JSONFragment testers;
    Hub[] hubs;
    JSONFragment antennas;
    JSONFragment bridges;
    Bridge[] bridges;
    JSONFragment chunk_types;
    Chunk[] chunks;
    JSONFragment toolboxes;
  10. CyberShadow revised this gist May 28, 2024. 3 changed files with 100 additions and 53 deletions.
    29 changes: 1 addition & 28 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -25,33 +25,6 @@ import game;

    ///////////////////////////////////////////////////////////////////////////////

    alias HostIndex = ubyte;

    Address.Element[4] toAddressElements(HostIndex index)
    {
    return [
    cast(Address.Element)((index >> (3 * 2)) & 3),
    cast(Address.Element)((index >> (2 * 2)) & 3),
    cast(Address.Element)((index >> (1 * 2)) & 3),
    cast(Address.Element)((index >> (0 * 2)) & 3),
    ];
    }

    Address toAddress(HostIndex index, Address.Type type)
    {
    return Address(toAddressElements(index), type);
    }

    Address wildcardAddress(Address.Type type)
    {
    return Address([
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    ], type);
    }

    ///////////////////////////////////////////////////////////////////////////////

    /// Returns a range of coordinates of chunks which fully contain or
    @@ -393,7 +366,7 @@ void buildThing(ref SaveGame game, Config config)
    {
    auto x0 = startWorld[0] + terminatorSize + sourceHost * cellSize;
    auto z0 = startWorld[2] + terminatorSize + targetHost * cellSize;
    auto active = config.rules.any!(rule => sourceHost.toAddressElements.matches(rule.source) && targetHost.toAddressElements.matches(rule.target));
    auto active = config.canSend[sourceHost][targetHost];
    if (active)
    {
    auto filterInputHub = addHub(
    69 changes: 44 additions & 25 deletions config.d
    Original file line number Diff line number Diff line change
    @@ -6,41 +6,60 @@ import game;

    struct Config
    {
    struct Rule
    {
    Address.Element[4] source, target;
    }
    Rule[] rules;
    bool[4^^4][4^^4] canSend;
    }

    Config readConfig()
    {
    Address.Element[4][] replierMasks;

    Config config;
    foreach (line; readText("rules.txt").splitLines)
    {
    if (!line.length || line[0] == '#')
    continue;
    auto rule = line.parseRule();
    config.rules ~= rule;
    auto parts = line.findSplit("\t");
    switch (parts[2])
    {
    case "reply":
    auto sourceMask = parseAddress(parts[0]);
    replierMasks ~= sourceMask;
    break;

    default:
    // expand letters
    void handle(string sourceStr, string targetStr)
    {
    auto sourceMask = parseAddress(sourceStr);
    auto targetMask = parseAddress(targetStr);
    foreach (source; hostsMatching(sourceMask))
    foreach (target; hostsMatching(targetMask))
    config.canSend[source][target] = true;
    }
    void expand(string sourceStr, string targetStr)
    {
    foreach (letter; "abcd")
    if (sourceStr.canFind(letter))
    {
    foreach (replacement; "0123")
    expand(
    sourceStr.replace(letter, replacement),
    targetStr.replace(letter, replacement),
    );
    return;
    }
    handle(sourceStr, targetStr);
    }
    expand(parts[0], parts[2]);
    break;
    }
    }
    return config;
    }

    Config.Rule parseRule(string line)
    {
    auto parts = line.findSplit("\t");
    auto source = parseAddress(parts[0]);
    auto target = parseAddress(parts[2]);
    return Config.Rule(source, target);
    }
    foreach (replierMask; replierMasks)
    foreach (replier; hostsMatching(replierMask))
    foreach (peer; allHosts)
    if (config.canSend[peer][replier])
    config.canSend[replier][peer] = true;

    bool matches(Address.Element[4] address, Address.Element[4] pattern)
    {
    foreach (i, elem; pattern)
    {
    assert(address[i] != Address.Element.Wildcard, "Wildcard not allowed in address");
    if (elem != Address.Element.Wildcard && elem != address[i])
    return false;
    }
    return true;
    return config;
    }
    55 changes: 55 additions & 0 deletions game.d
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,7 @@
    import std.algorithm.iteration;
    import std.array;
    import std.math.rounding;
    import std.range;
    import std.traits;
    import std.typecons;

    @@ -264,3 +265,57 @@ Address.Element[4] parseAddress(string str)

    string toString(Address.Element element) { return "0123*"[element .. element+1]; }
    string toString(Address.Element[4] address) { return address[].map!toString.join("."); }

    ///////////////////////////////////////////////////////////////////////////////

    alias HostIndex = ubyte;

    Address.Element[4] toAddressElements(HostIndex index)
    {
    return [
    cast(Address.Element)((index >> (3 * 2)) & 3),
    cast(Address.Element)((index >> (2 * 2)) & 3),
    cast(Address.Element)((index >> (1 * 2)) & 3),
    cast(Address.Element)((index >> (0 * 2)) & 3),
    ];
    }

    Address toAddress(HostIndex index, Address.Type type)
    {
    return Address(toAddressElements(index), type);
    }

    Address wildcardAddress(Address.Type type)
    {
    return Address([
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    ], type);
    }

    ///////////////////////////////////////////////////////////////////////////////

    bool matches(Address.Element[4] address, Address.Element[4] pattern)
    {
    foreach (i, elem; pattern)
    {
    assert(address[i] != Address.Element.Wildcard, "Wildcard not allowed in address");
    if (elem != Address.Element.Wildcard && elem != address[i])
    return false;
    }
    return true;
    }

    auto allHosts()
    {
    return (4^^4)
    .iota
    .map!(hostIndex => cast(HostIndex)hostIndex);
    }

    auto hostsMatching(Address.Element[4] mask)
    {
    return allHosts.filter!(hostIndex => hostIndex.toAddressElements.matches(mask));
    }
  11. CyberShadow revised this gist May 28, 2024. 1 changed file with 18 additions and 2 deletions.
    20 changes: 18 additions & 2 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,7 @@ import std.algorithm.comparison;
    import std.algorithm.iteration;
    import std.algorithm.mutation;
    import std.algorithm.searching;
    import std.algorithm.setops;
    import std.algorithm.sorting;
    import std.array;
    import std.file;
    @@ -10,6 +11,7 @@ import std.math.algebraic;
    import std.math.constants;
    import std.math.rounding;
    import std.path;
    import std.range;
    import std.stdio : stderr;
    import std.typecons;

    @@ -52,6 +54,18 @@ Address wildcardAddress(Address.Type type)

    ///////////////////////////////////////////////////////////////////////////////

    /// Returns a range of coordinates of chunks which fully contain or
    /// touch (on a face, edge, or corner) the given world coordinate.
    auto chunksTouching(WorldCoord pos)
    {
    auto chunkPos = pos.toChunk;
    return cartesianProduct(
    iota(chunkPos.x - (pos[0] % chunkSize == 0 ? 1 : 0), chunkPos.x + 1),
    iota(chunkPos.y - (pos[1] % chunkSize == 0 ? 1 : 0), chunkPos.y + 1),
    iota(chunkPos.z - (pos[2] % chunkSize == 0 ? 1 : 0), chunkPos.z + 1),
    ).map!(t => ChunkCoord(t.expand));
    }

    void buildThing(ref SaveGame game, Config config)
    {
    const origGame = game;
    @@ -146,8 +160,10 @@ void buildThing(ref SaveGame game, Config config)
    game.nodes = game.nodes.dup;
    foreach (NodeIndex i, ref node; game.nodes)
    if (node.pos[1] == objectY &&
    node.pos.toChunk in chunkPlan &&
    chunkPlan[node.pos.toChunk].firstRunOnly == false)
    chunksTouching(node.pos).any!(chunkPos =>
    chunkPos in chunkPlan &&
    chunkPlan[chunkPos].firstRunOnly == false
    ))
    {
    node = nullNode;
    nodeFreeList ~= i;
  12. CyberShadow revised this gist May 28, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -179,12 +179,12 @@ void buildThing(ref SaveGame game, Config config)
    Material.crackedStone,
    ];
    foreach (chunk; game.chunks)
    if (chunk[0] in chunkPlan)
    if (chunk[0] in chunkRunPlan)
    foreach (r; chunk[1])
    if (!unimportantMaterials.canFind(cast(Material)r[1]))
    throw new Exception("Refusing to overwrite chunk at %s with important material %s".format(chunk[0], cast(Material)r[1]));
    foreach (node; game.nodes)
    if (node.pos[1] != objectY && node.pos.toChunk in chunkPlan)
    if (node.pos[1] != objectY && node.pos.toChunk in chunkRunPlan)
    throw new Exception("Refusing to overwrite chunk at %s with user-placed node at %s".format(node.pos.toChunk, node.pos));

    game.chunks = game.chunks
  13. CyberShadow revised this gist May 28, 2024. 1 changed file with 12 additions and 3 deletions.
    15 changes: 12 additions & 3 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -509,13 +509,22 @@ void buildThing(ref SaveGame game, Config config)

    foreach (HostIndex host; 0 .. numHosts)
    {
    enum hostsPerBusArm = 16;
    auto exitX = startWorld[0] + terminatorSize + host * cellSize + cellSize * 1/8; // Same as inputFilter
    auto busLaneWidth = double(chunkSize) / numHosts;
    auto busExitZ = busChunkZ * chunkSize;
    auto busZ = busExitZ + host * busLaneWidth + busLaneWidth / 2;
    auto busEnterLaneWidth = double(chunkSize) / 16; // 1
    auto enterX = busTargets[host / 16].x * chunkSize + (host % 16) * busEnterLaneWidth + busEnterLaneWidth / 2;
    auto enterZ = busTargets[host / 16].z * chunkSize + (busTargets[host / 16].z > busChunkZ ? 0 : 1) * chunkSize;
    auto busEnterArmWidth = double(chunkSize) / 4 * 3;
    auto busEnterLaneWidth = busEnterArmWidth / hostsPerBusArm; // 0.75 (edges are too close to dig areas and might even be effectively buried)
    auto enterX =
    busTargets[host / hostsPerBusArm].x * chunkSize // bus arm position
    +
    (chunkSize - busEnterArmWidth) / 2 // centering within bus arm
    +
    (host % hostsPerBusArm) * busEnterLaneWidth // per-host offset
    +
    busEnterLaneWidth / 2; // centering within bus lane
    auto enterZ = busTargets[host / hostsPerBusArm].z * chunkSize + (busTargets[host / hostsPerBusArm].z > busChunkZ ? 0 : 1) * chunkSize;

    auto busExit = addRelay([exitX, objectY, busExitZ]);
    auto busFinish = addRelay([exitX, objectY, busZ]);
  14. CyberShadow revised this gist May 28, 2024. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -183,6 +183,9 @@ void buildThing(ref SaveGame game, Config config)
    foreach (r; chunk[1])
    if (!unimportantMaterials.canFind(cast(Material)r[1]))
    throw new Exception("Refusing to overwrite chunk at %s with important material %s".format(chunk[0], cast(Material)r[1]));
    foreach (node; game.nodes)
    if (node.pos[1] != objectY && node.pos.toChunk in chunkPlan)
    throw new Exception("Refusing to overwrite chunk at %s with user-placed node at %s".format(node.pos.toChunk, node.pos));

    game.chunks = game.chunks
    .filter!(c => c[0] !in chunkRunPlan)
  15. CyberShadow revised this gist May 28, 2024. 1 changed file with 13 additions and 0 deletions.
    13 changes: 13 additions & 0 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -171,6 +171,19 @@ void buildThing(ref SaveGame game, Config config)

    // Make chunks
    {
    static immutable Material[] unimportantMaterials = [
    Material.empty,
    Material.dirt,
    Material.drillGoo,
    Material.stone,
    Material.crackedStone,
    ];
    foreach (chunk; game.chunks)
    if (chunk[0] in chunkPlan)
    foreach (r; chunk[1])
    if (!unimportantMaterials.canFind(cast(Material)r[1]))
    throw new Exception("Refusing to overwrite chunk at %s with important material %s".format(chunk[0], cast(Material)r[1]));

    game.chunks = game.chunks
    .filter!(c => c[0] !in chunkRunPlan)
    .array;
  16. CyberShadow revised this gist May 28, 2024. 2 changed files with 36 additions and 6 deletions.
    6 changes: 3 additions & 3 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -186,10 +186,10 @@ void buildThing(ref SaveGame game, Config config)
    {
    case ChunkGeometry.flat:
    return y >= 12 && y < 30
    ? 0
    : 1;
    ? Material.empty
    : Material.dirt;
    case ChunkGeometry.empty:
    return 0;
    return Material.empty;
    }
    }();

    36 changes: 33 additions & 3 deletions game.d
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,7 @@
    import std.algorithm.iteration;
    import std.array;
    import std.math.rounding;
    import std.traits;
    import std.typecons;

    import ae.utils.json;
    @@ -121,10 +122,39 @@ struct Hub

    struct ChunkCoord { int x, y, z; }
    alias ChunkRunLength = ushort;
    alias Material = ubyte;
    enum Material : ubyte
    {
    empty,
    dirt, // diggable
    packedDirt,
    softGrass, // diggable; with trees
    grass,
    indestructibleStone,
    steelPlates,
    ceramicTiles,
    flatSteelPlates,
    stoneBricks, // looks like cobblestone from above
    drillGoo, // diggable; user-placed
    cobblestone,
    mosaic, // looks like cobblestone from above; black on red
    carpet, // looks like cobblestone from above; brown on red
    woodPlanks,
    crackedCeramicTiles,
    leakingSteelPlates,
    empty2,
    lava,
    empty3,
    water, // invisible; makes splashing sounds and screen warp effect when submerged
    water2, // ditto
    empty4,
    // ... lots of water variations go here ...
    // ... TODO ...
    stone = 43,
    crackedStone = 44,
    }
    alias ChunkRun = Tuple!(
    ChunkRunLength,
    Material,
    OriginalType!Material,
    );

    alias ChunkCompressedContents = ChunkRun[];
    @@ -169,7 +199,7 @@ ChunkContents decompress(const ChunkCompressedContents compressed)
    auto v = r[1];
    foreach (i; 0 .. count)
    {
    cube[p / 32 / 32][p / 32 % 32][p % 32] = v;
    cube[p / 32 / 32][p / 32 % 32][p % 32] = cast(Material)v;
    p++;
    }
    }
  17. CyberShadow revised this gist May 28, 2024. 1 changed file with 61 additions and 0 deletions.
    61 changes: 61 additions & 0 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    import std.algorithm.comparison;
    import std.algorithm.iteration;
    import std.algorithm.mutation;
    import std.algorithm.searching;
    import std.algorithm.sorting;
    import std.array;
    @@ -81,6 +82,14 @@ void buildThing(ref SaveGame game, Config config)
    auto startWorld = startChunk.toWorld;
    auto objectY = startWorld[1] + 5.5; // We will place all objects at this height.

    // Note: these target the landing pad (a chunk that we will clear on the first run only, and not populate)
    static immutable ChunkCoord[4] busTargets = [
    { 5, 0, 5},
    { 8, -1, 16},
    {19, -1, 11},
    {35, -1, 7},
    ];

    // Lay out the chunk geometry

    enum ChunkGeometry { flat, empty }
    @@ -92,6 +101,34 @@ void buildThing(ref SaveGame game, Config config)
    chunkPlan[ChunkCoord(x, startChunk.y, z)] = ChunkPlan(ChunkGeometry.flat, false);
    foreach (x; startChunk.x .. startChunk.x + totalChunks)
    chunkPlan[ChunkCoord(x, startChunk.y, startChunk.z + totalChunks)] = ChunkPlan(ChunkGeometry.flat, true);
    // Bus
    auto busChunkZ = startChunk.z + totalChunks + 1;
    foreach (x; startChunk.x .. startChunk.x + totalChunks)
    chunkPlan[ChunkCoord(x, startChunk.y, busChunkZ)] = ChunkPlan(ChunkGeometry.flat, false);
    foreach (i, target; busTargets)
    {
    {
    int x0 = target.x;
    int x1 = startChunk.x;
    if (x0 > x1) swap(x0, x1);
    foreach (x; x0 .. x1 + 1)
    chunkPlan[ChunkCoord(x, startChunk.y, busChunkZ)] = ChunkPlan(ChunkGeometry.flat, false);
    }
    {
    int z0 = target.z;
    int z1 = busChunkZ;
    if (z0 > z1) z0--; else z0++;
    if (z0 > z1) swap(z0, z1);
    foreach (z; z0 .. z1 + 1)
    chunkPlan[ChunkCoord(target.x, startChunk.y, z)] = ChunkPlan(ChunkGeometry.flat, false);
    }
    {
    auto padGeom = target.y >= startChunk.y
    ? ChunkGeometry.flat
    : ChunkGeometry.empty;
    chunkPlan[ChunkCoord(target.x, startChunk.y, target.z)] = ChunkPlan(padGeom, true);
    }
    }

    bool firstRun = !game.nodes.canFind!(node => node.pos.toChunk in chunkPlan && node.pos[1] == objectY);
    auto chunkRunPlan = chunkPlan
    @@ -451,6 +488,30 @@ void buildThing(ref SaveGame game, Config config)
    foreach (HostIndex host; 0 .. numHosts)
    reconnect(peerPorts[host]);
    }

    // The bus

    foreach (HostIndex host; 0 .. numHosts)
    {
    auto exitX = startWorld[0] + terminatorSize + host * cellSize + cellSize * 1/8; // Same as inputFilter
    auto busLaneWidth = double(chunkSize) / numHosts;
    auto busExitZ = busChunkZ * chunkSize;
    auto busZ = busExitZ + host * busLaneWidth + busLaneWidth / 2;
    auto busEnterLaneWidth = double(chunkSize) / 16; // 1
    auto enterX = busTargets[host / 16].x * chunkSize + (host % 16) * busEnterLaneWidth + busEnterLaneWidth / 2;
    auto enterZ = busTargets[host / 16].z * chunkSize + (busTargets[host / 16].z > busChunkZ ? 0 : 1) * chunkSize;

    auto busExit = addRelay([exitX, objectY, busExitZ]);
    auto busFinish = addRelay([exitX, objectY, busZ]);
    auto busStart = addRelay([enterX, objectY, busZ]);
    auto busEnter = addRelay([enterX, objectY, enterZ]);

    reconnect(NodePort(busEnter, 0));
    addRelays(NodePort(busEnter, 1), NodePort(busStart, 0));
    addRelays(NodePort(busStart, 1), NodePort(busFinish, 0));
    addRelays(NodePort(busFinish, 1), NodePort(busExit, 0));
    reconnect(NodePort(busExit, 1));
    }
    }

    void main()
  18. CyberShadow revised this gist May 28, 2024. 1 changed file with 44 additions and 27 deletions.
    71 changes: 44 additions & 27 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,7 @@ import std.math.constants;
    import std.math.rounding;
    import std.path;
    import std.stdio : stderr;
    import std.typecons;

    import ae.sys.file;
    import ae.utils.array;
    @@ -80,6 +81,25 @@ void buildThing(ref SaveGame game, Config config)
    auto startWorld = startChunk.toWorld;
    auto objectY = startWorld[1] + 5.5; // We will place all objects at this height.

    // Lay out the chunk geometry

    enum ChunkGeometry { flat, empty }
    struct ChunkPlan { ChunkGeometry geometry; bool firstRunOnly; }
    ChunkPlan[ChunkCoord] chunkPlan;
    // Switch
    foreach (z; startChunk.z .. startChunk.z + totalChunks)
    foreach (x; startChunk.x .. startChunk.x + totalChunks)
    chunkPlan[ChunkCoord(x, startChunk.y, z)] = ChunkPlan(ChunkGeometry.flat, false);
    foreach (x; startChunk.x .. startChunk.x + totalChunks)
    chunkPlan[ChunkCoord(x, startChunk.y, startChunk.z + totalChunks)] = ChunkPlan(ChunkGeometry.flat, true);

    bool firstRun = !game.nodes.canFind!(node => node.pos.toChunk in chunkPlan && node.pos[1] == objectY);
    auto chunkRunPlan = chunkPlan
    .byKeyValue
    .filter!(kv => !kv.value.firstRunOnly || firstRun)
    .map!(kv => tuple(kv.key, kv.value.geometry))
    .assocArray;

    static immutable nullNode = Node([0, 0, 0], [0, 0, 0], 0);

    // Delete nodes, edges, and objects in the build zone
    @@ -88,9 +108,9 @@ void buildThing(ref SaveGame game, Config config)
    {
    game.nodes = game.nodes.dup;
    foreach (NodeIndex i, ref node; game.nodes)
    if (node.pos[0] >= startWorld[0] && node.pos[0] < startWorld[0] + totalSize &&
    node.pos[1] == objectY &&
    node.pos[2] >= startWorld[2] && node.pos[2] < startWorld[2] + totalSize)
    if (node.pos[1] == objectY &&
    node.pos.toChunk in chunkPlan &&
    chunkPlan[node.pos.toChunk].firstRunOnly == false)
    {
    node = nullNode;
    nodeFreeList ~= i;
    @@ -112,35 +132,32 @@ void buildThing(ref SaveGame game, Config config)
    }
    // scope(success) enforce(nodeFreeList.length == 0, "Still have deleted nodes");

    bool firstRun = true;
    if (nodeFreeList.length)
    firstRun = false;

    // Make chunks
    {
    enum chunkX0 = startChunk.x;
    enum chunkY = startChunk.y;
    enum chunkZ0 = startChunk.z;

    auto chunksToMake = totalChunks + firstRun;

    game.chunks = game.chunks
    .filter!(c => !(c[0].x >= chunkX0 && c[0].x < chunkX0 + chunksToMake && c[0].y == chunkY && c[0].z >= chunkZ0 && c[0].z < chunkZ0 + chunksToMake))
    .filter!(c => c[0] !in chunkRunPlan)
    .array;

    foreach (chunkZ; chunkZ0 .. chunkZ0 + chunksToMake)
    foreach (chunkX; chunkX0 .. chunkX0 + chunksToMake)
    {
    ChunkContents c;
    foreach (z, ref plane; c)
    foreach (y, ref row; plane)
    foreach (x, ref cell; row)
    cell = y >= 12 && y < 30
    ? 0
    : 1;

    game.chunks ~= Chunk(ChunkCoord(chunkX, chunkY, chunkZ), compress(c));
    }
    foreach (chunkPos, geometry; chunkRunPlan)
    {
    ChunkContents c;
    foreach (z, ref plane; c)
    foreach (y, ref row; plane)
    foreach (x, ref cell; row)
    cell = delegate Material {
    final switch (geometry)
    {
    case ChunkGeometry.flat:
    return y >= 12 && y < 30
    ? 0
    : 1;
    case ChunkGeometry.empty:
    return 0;
    }
    }();

    game.chunks ~= Chunk(chunkPos, compress(c));
    }
    }

    static immutable WorldCoord upVector = [0, 1, 0];
  19. CyberShadow revised this gist May 27, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion build.d
    Original file line number Diff line number Diff line change
    @@ -66,7 +66,7 @@ void buildThing(ref SaveGame game, Config config)
    // Cheat to save FPS. Wires start to visually disappear at around 40.
    enum maxWireLength = 32;

    enum ChunkCoord startChunk = {x: 5, y: 0, z: 8};
    enum ChunkCoord startChunk = {x: 11, y: 0, z: -2};

    auto totalSize =
    terminatorSize +
  20. CyberShadow revised this gist May 27, 2024. 1 changed file with 1 addition and 15 deletions.
    16 changes: 1 addition & 15 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -327,19 +327,6 @@ void buildThing(ref SaveGame game, Config config)
    collision: Filter.Config.Collision.SendBackOutbound,
    ),
    );
    auto killSwitchFilter = addFilter(
    pos: [x0 + cellSize * 0.725, objectY, z0 + cellSize * 0.725],
    angle: 0,
    config: Filter.Config(
    // By default, rejects everything, but allows easy reconfiguration to allow everything.
    port: 0,
    mask: wildcardAddress(Address.Type.UnrestrictedFilter),
    addr: Filter.Config.Addr.Dst,
    action: Filter.Config.Action.SendBackPacket,
    op: Filter.Config.Op.Match, // User can toggle this to allow everything.
    collision: Filter.Config.Collision.SendBackOutbound,
    ),
    );
    auto oneWayOutputFilter = addFilter(
    pos: [x0 + cellSize * 0.725, objectY, z0 + cellSize * 0.4125],
    angle: PI/2,
    @@ -366,8 +353,7 @@ void buildThing(ref SaveGame game, Config config)
    angle: PI,
    );
    addEdge(NodePort(filterInputHub, 1), NodePort(targetCheckFilter, 0));
    addEdge(NodePort(targetCheckFilter, 1), NodePort(killSwitchFilter, 0));
    addEdge(NodePort(killSwitchFilter, 1), NodePort(oneWayOutputFilter, 0));
    addEdge(NodePort(targetCheckFilter, 1), NodePort(oneWayOutputFilter, 0));
    addEdge(NodePort(oneWayOutputFilter, 1), NodePort(filterOutputHub, 1));

    // Link with top/right neighbor
  21. CyberShadow revised this gist May 27, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion build.d
    Original file line number Diff line number Diff line change
    @@ -74,7 +74,7 @@ void buildThing(ref SaveGame game, Config config)
    portLinkSize +
    portLaneSize * numHosts +
    minPortFilterSize;
    auto totalChunks = cast(uint)ceil(totalSize / chunkSize);
    auto totalChunks = cast(int)ceil(totalSize / chunkSize);
    totalSize = totalChunks * chunkSize;

    auto startWorld = startChunk.toWorld;
  22. CyberShadow revised this gist May 27, 2024. 1 changed file with 22 additions and 28 deletions.
    50 changes: 22 additions & 28 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -62,7 +62,10 @@ void buildThing(ref SaveGame game, Config config)
    enum portLaneSize = 0.25;
    enum minPortFilterSize = 4;

    enum maxWireLength = 4.0;
    // enum maxWireLength = 4.0;
    // Cheat to save FPS. Wires start to visually disappear at around 40.
    enum maxWireLength = 32;

    enum ChunkCoord startChunk = {x: 5, y: 0, z: 8};

    auto totalSize =
    @@ -200,34 +203,25 @@ void buildThing(ref SaveGame game, Config config)

    void addRelays(NodePort source, NodePort target, CableColor color = 0)
    {
    bool honest = false;
    if (honest)
    {
    while (true)
    {
    auto sourceCoord = game.nodes[source[0]].pos;
    auto targetCoord = game.nodes[target[0]].pos;
    WorldCoord vec = [
    targetCoord[0] - sourceCoord[0],
    targetCoord[1] - sourceCoord[1],
    targetCoord[2] - sourceCoord[2],
    ];
    auto vecLength = sqrt(vec[0] ^^ 2 + vec[1] ^^ 2 + vec[2] ^^ 2);
    if (vecLength < maxWireLength)
    return addEdge(source, target, color);
    vec[] /= vecLength;
    auto relayCoord = sourceCoord;
    relayCoord[] += vec[] * maxWireLength;
    auto relay = addRelay(relayCoord);
    addEdge(source, NodePort(relay, 0), color);

    source = NodePort(relay, 1);
    }
    }
    else
    while (true)
    {
    // Cheat to save FPS
    addEdge(source, target, color);
    auto sourceCoord = game.nodes[source[0]].pos;
    auto targetCoord = game.nodes[target[0]].pos;
    WorldCoord vec = [
    targetCoord[0] - sourceCoord[0],
    targetCoord[1] - sourceCoord[1],
    targetCoord[2] - sourceCoord[2],
    ];
    auto vecLength = sqrt(vec[0] ^^ 2 + vec[1] ^^ 2 + vec[2] ^^ 2);
    if (vecLength <= maxWireLength)
    return addEdge(source, target, color);
    vec[] /= vecLength;
    auto relayCoord = sourceCoord;
    relayCoord[] += vec[] * maxWireLength;
    auto relay = addRelay(relayCoord);
    addEdge(source, NodePort(relay, 0), color);

    source = NodePort(relay, 1);
    }
    }

  23. CyberShadow revised this gist May 27, 2024. 1 changed file with 19 additions and 83 deletions.
    102 changes: 19 additions & 83 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -251,21 +251,17 @@ void buildThing(ref SaveGame game, Config config)
    assert(numAddedEdges <= 1, "Ambiguous reconnect");
    }

    // Create the router
    // Create the switch
    {
    enum Side
    {
    source,
    target,
    }

    NodePort[numHosts][enumLength!Side] cellPorts;

    // Terminators
    // Connect to (hub, 0)
    struct Terminator
    {
    NodePort hub;
    }
    Terminator[numHosts][enumLength!Side] terminators;
    foreach (side; Side.init .. enumLength!Side)
    {
    WorldCoord sidePos(WorldCoord pos)
    @@ -306,19 +302,12 @@ void buildThing(ref SaveGame game, Config config)
    addEdge(NodePort(hub, 1), NodePort(relay0, 0));
    addEdge(NodePort(relay0, 1), NodePort(relay1, 0));
    addEdge(NodePort(relay1, 1), NodePort(hub, 2));
    terminators[side][host] = Terminator(
    hub: NodePort(hub, 0),
    );

    cellPorts[side][host] = NodePort(hub, 0);
    }
    }

    // NxN router cells
    struct Cell
    {
    NodePort inputUp, inputDown, outputLeft, outputRight;
    }
    Cell[numHosts][numHosts] cells;

    foreach (HostIndex sourceHost; 0 .. numHosts)
    foreach (HostIndex targetHost; 0 .. numHosts)
    {
    @@ -386,38 +375,18 @@ void buildThing(ref SaveGame game, Config config)
    addEdge(NodePort(targetCheckFilter, 1), NodePort(killSwitchFilter, 0));
    addEdge(NodePort(killSwitchFilter, 1), NodePort(oneWayOutputFilter, 0));
    addEdge(NodePort(oneWayOutputFilter, 1), NodePort(filterOutputHub, 1));
    cells[sourceHost][targetHost] = Cell(
    inputUp: NodePort(filterInputHub, 2),
    inputDown: NodePort(filterInputHub, 0),
    outputLeft: NodePort(filterOutputHub, 0),
    outputRight: NodePort(filterOutputHub, 2),
    );
    }
    else
    {
    auto inputRelay = addRelay(
    pos: [x0 + cellSize * 1/8, objectY, z0 + cellSize * 5/8],
    angle: -PI/2,
    );
    auto outputRelay = addRelay(
    pos: [x0 + cellSize * 5/8, objectY, z0 + cellSize * 1/8],
    angle: PI,
    );
    cells[sourceHost][targetHost] = Cell(
    inputUp: NodePort(inputRelay, 1),
    inputDown: NodePort(inputRelay, 0),
    outputLeft: NodePort(outputRelay, 0),
    outputRight: NodePort(outputRelay, 1),
    );

    // Link with top/right neighbor
    addRelays(cellPorts[Side.source][sourceHost], NodePort(filterInputHub, 2));
    cellPorts[Side.source][sourceHost] = NodePort(filterInputHub, 0);

    addRelays(cellPorts[Side.target][targetHost], NodePort(filterOutputHub, 0));
    cellPorts[Side.target][targetHost] = NodePort(filterOutputHub, 2);
    }
    }

    // Ingress/egress port
    struct Port
    {
    NodePort peer, inputChain, outputChain;
    }
    Port[numHosts] ports;
    // Switch edge port to endpoint
    NodePort[numHosts] peerPorts;

    foreach (HostIndex host; 0 .. numHosts)
    {
    @@ -475,48 +444,15 @@ void buildThing(ref SaveGame game, Config config)
    addRelays(NodePort(outputToCornerRelay, 1), NodePort(cornerRelay, 0));
    addRelays(NodePort(cornerRelay, 1), NodePort(inputOutputSplitHub, 2));

    ports[host] = Port(
    peer: NodePort(inputFilter, 0),
    inputChain: NodePort(inputLoopFilter, 1),
    outputChain: NodePort(outputRelay, 0),
    );
    }

    // Wire everything together
    addRelays(cellPorts[Side.source][host], NodePort(inputLoopFilter, 1));
    addRelays(cellPorts[Side.target][host], NodePort(outputRelay, 0));

    // Input chain
    foreach (HostIndex sourceHost; 0 .. numHosts)
    foreach (HostIndex targetHost; 0 .. numHosts)
    {
    // Connect to terminator
    if (targetHost == 0)
    addEdge(cells[sourceHost][targetHost].inputUp, terminators[Side.source][sourceHost].hub);
    // Daisy-chain
    if (targetHost > 0)
    addEdge(cells[sourceHost][targetHost - 1].inputDown, cells[sourceHost][targetHost].inputUp);
    // Connect to input port
    if (targetHost + 1 == numHosts)
    addEdge(ports[sourceHost].inputChain, cells[sourceHost][targetHost].inputDown);
    }

    // Output chain
    foreach (HostIndex sourceHost; 0 .. numHosts)
    foreach (HostIndex targetHost; 0 .. numHosts)
    {
    // Connect to terminator
    if (sourceHost == 0)
    addEdge(cells[sourceHost][targetHost].outputLeft, terminators[Side.target][targetHost].hub);
    // Daisy-chain
    if (sourceHost > 0)
    addEdge(cells[sourceHost - 1][targetHost].outputRight, cells[sourceHost][targetHost].outputLeft);
    // Connect to output port
    if (sourceHost + 1 == numHosts)
    addEdge(cells[sourceHost][targetHost].outputRight, ports[targetHost].outputChain);
    }
    peerPorts[host] = NodePort(inputFilter, 0);
    }

    // Reconnect peers
    foreach (HostIndex host; 0 .. numHosts)
    reconnect(ports[host].peer);
    reconnect(peerPorts[host]);
    }
    }

  24. CyberShadow revised this gist May 27, 2024. 3 changed files with 86 additions and 61 deletions.
    34 changes: 33 additions & 1 deletion build.d
    Original file line number Diff line number Diff line change
    @@ -1,13 +1,15 @@
    import std.algorithm.comparison;
    import std.algorithm.iteration;
    import std.algorithm.searching;
    import std.algorithm.sorting;
    import std.array;
    import std.file;
    import std.format;
    import std.math.algebraic;
    import std.math.constants;
    import std.math.rounding;
    import std.path;
    import std.stdio : stderr;

    import ae.sys.file;
    import ae.utils.array;
    @@ -17,6 +19,37 @@ import ae.utils.meta;
    import config;
    import game;

    ///////////////////////////////////////////////////////////////////////////////

    alias HostIndex = ubyte;

    Address.Element[4] toAddressElements(HostIndex index)
    {
    return [
    cast(Address.Element)((index >> (3 * 2)) & 3),
    cast(Address.Element)((index >> (2 * 2)) & 3),
    cast(Address.Element)((index >> (1 * 2)) & 3),
    cast(Address.Element)((index >> (0 * 2)) & 3),
    ];
    }

    Address toAddress(HostIndex index, Address.Type type)
    {
    return Address(toAddressElements(index), type);
    }

    Address wildcardAddress(Address.Type type)
    {
    return Address([
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    ], type);
    }

    ///////////////////////////////////////////////////////////////////////////////

    void buildThing(ref SaveGame game, Config config)
    {
    const origGame = game;
    @@ -466,7 +499,6 @@ void buildThing(ref SaveGame game, Config config)
    addEdge(ports[sourceHost].inputChain, cells[sourceHost][targetHost].inputDown);
    }


    // Output chain
    foreach (HostIndex sourceHost; 0 .. numHosts)
    foreach (HostIndex targetHost; 0 .. numHosts)
    18 changes: 0 additions & 18 deletions config.d
    Original file line number Diff line number Diff line change
    @@ -34,24 +34,6 @@ Config.Rule parseRule(string line)
    return Config.Rule(source, target);
    }

    Address.Element[4] parseAddress(string str)
    {
    Address.Element[4] result;
    foreach (i, part; str.split("."))
    result[i] = {
    switch (part)
    {
    case "*": return Address.Element.Wildcard;
    case "0": return Address.Element.Zero;
    case "1": return Address.Element.One;
    case "2": return Address.Element.Two;
    case "3": return Address.Element.Three;
    default: throw new Exception("Invalid address element: " ~ part);
    }
    }();
    return result;
    }

    bool matches(Address.Element[4] address, Address.Element[4] pattern)
    {
    foreach (i, elem; pattern)
    95 changes: 53 additions & 42 deletions game.d
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    import std.algorithm.iteration;
    import std.array;
    import std.math.rounding;
    import std.typecons;

    @@ -33,24 +35,6 @@ alias CableColor = ubyte;
    alias PortIndex = ubyte;
    alias NodeIndex = size_t;

    alias NodePort = Tuple!(
    NodeIndex,
    PortIndex,
    );

    alias Edge = Tuple!(
    NodePort,
    NodePort,
    CableColor,
    );

    struct Relay
    {
    NodeIndex node;
    bool fixed;
    bool light;
    }

    struct Address
    {
    enum Element
    @@ -71,6 +55,39 @@ struct Address
    Type address_type;
    }

    alias NodePort = Tuple!(
    NodeIndex,
    PortIndex,
    );

    alias Edge = Tuple!(
    NodePort,
    NodePort,
    CableColor,
    );

    enum Infection
    {
    Bio,
    StrongBio,
    Hack,
    }

    struct Endpoint
    {
    NodeIndex node;
    Address address;
    Nullable!Infection infection;
    bool disinfection;
    }

    struct Relay
    {
    NodeIndex node;
    bool fixed;
    bool light;
    }

    struct Filter
    {
    NodeIndex node;
    @@ -123,7 +140,7 @@ struct SaveGame
    JSONFragment story;
    Node[] nodes;
    Edge[] edges;
    JSONFragment endpoints;
    Endpoint[] endpoints;
    Relay[] relays;
    Filter[] filters;
    JSONFragment testers;
    @@ -197,29 +214,23 @@ ChunkCoord toChunk(WorldCoord c)

    ///////////////////////////////////////////////////////////////////////////////

    alias HostIndex = ubyte;

    Address.Element[4] toAddressElements(HostIndex index)
    {
    return [
    cast(Address.Element)((index >> (3 * 2)) & 3),
    cast(Address.Element)((index >> (2 * 2)) & 3),
    cast(Address.Element)((index >> (1 * 2)) & 3),
    cast(Address.Element)((index >> (0 * 2)) & 3),
    ];
    }

    Address toAddress(HostIndex index, Address.Type type)
    Address.Element[4] parseAddress(string str)
    {
    return Address(toAddressElements(index), type);
    Address.Element[4] result;
    foreach (i, part; str.split("."))
    result[i] = {
    switch (part)
    {
    case "*": return Address.Element.Wildcard;
    case "0": return Address.Element.Zero;
    case "1": return Address.Element.One;
    case "2": return Address.Element.Two;
    case "3": return Address.Element.Three;
    default: throw new Exception("Invalid address element: " ~ part);
    }
    }();
    return result;
    }

    Address wildcardAddress(Address.Type type)
    {
    return Address([
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    Address.Element.Wildcard,
    ], type);
    }
    string toString(Address.Element element) { return "0123*"[element .. element+1]; }
    string toString(Address.Element[4] address) { return address[].map!toString.join("."); }
  25. CyberShadow revised this gist May 27, 2024. 1 changed file with 26 additions and 0 deletions.
    26 changes: 26 additions & 0 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -19,6 +19,8 @@ import game;

    void buildThing(ref SaveGame game, Config config)
    {
    const origGame = game;

    // Configuration
    enum numHosts = 4 ^^ 3;
    enum terminatorSize = 4.0;
    @@ -196,6 +198,26 @@ void buildThing(ref SaveGame game, Config config)
    }
    }

    void reconnect(NodePort newPort)
    {
    auto prevNumEdges = game.edges.length;
    auto newNodeIndex = newPort[0];
    auto newNode = game.nodes[newNodeIndex];
    foreach (oldNodeIndex, oldNode; origGame.nodes)
    if (oldNode.pos == newNode.pos)
    {
    auto oldPort = NodePort(oldNodeIndex, newPort[1]);
    foreach (oldEdge; origGame.edges)
    if (oldEdge[0] == oldPort && !nodeDeleted[oldEdge[1][0]])
    addEdge(newPort, oldEdge[1]);
    else
    if (oldEdge[1] == oldPort && !nodeDeleted[oldEdge[0][0]])
    addEdge(oldEdge[0], newPort);
    }
    auto numAddedEdges = game.edges.length - prevNumEdges;
    assert(numAddedEdges <= 1, "Ambiguous reconnect");
    }

    // Create the router
    {
    enum Side
    @@ -459,6 +481,10 @@ void buildThing(ref SaveGame game, Config config)
    if (sourceHost + 1 == numHosts)
    addEdge(cells[sourceHost][targetHost].outputRight, ports[targetHost].outputChain);
    }

    // Reconnect peers
    foreach (HostIndex host; 0 .. numHosts)
    reconnect(ports[host].peer);
    }
    }

  26. CyberShadow revised this gist May 27, 2024. 1 changed file with 29 additions and 12 deletions.
    41 changes: 29 additions & 12 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,7 @@ import std.algorithm.iteration;
    import std.algorithm.searching;
    import std.array;
    import std.file;
    import std.format;
    import std.math.algebraic;
    import std.math.constants;
    import std.math.rounding;
    @@ -41,25 +42,30 @@ void buildThing(ref SaveGame game, Config config)
    auto startWorld = startChunk.toWorld;
    auto objectY = startWorld[1] + 5.5; // We will place all objects at this height.

    bool firstRun = true;

    static immutable nullNode = Node([0, 0, 0], [0, 0, 0], 0);

    // Delete nodes, edges, and objects in the build zone
    // 1. Delete the nodes themselves
    NodeIndex[] nodeFreeList;
    {
    auto nodeDeleted = new bool[game.nodes.length];
    foreach (i, ref node; game.nodes)
    game.nodes = game.nodes.dup;
    foreach (NodeIndex i, ref node; game.nodes)
    if (node.pos[0] >= startWorld[0] && node.pos[0] < startWorld[0] + totalSize &&
    node.pos[1] == objectY &&
    node.pos[2] >= startWorld[2] && node.pos[2] < startWorld[2] + totalSize)
    {
    node = nullNode;
    nodeDeleted[i] = true;
    nodeFreeList ~= i;

    firstRun = false;
    }
    }

    // 2. Map deleted nodes
    auto nodeDeleted = new bool[game.nodes.length];
    foreach (NodeIndex i; nodeFreeList)
    nodeDeleted[i] = true;

    // 3. Delete affected objects
    {

    game.edges = game.edges .filter!(edge => !(nodeDeleted[edge[0][0]] || nodeDeleted[edge[1][0]])).array;
    game.relays = game.relays .filter!(o => !nodeDeleted[o.node]).array;
    @@ -68,6 +74,10 @@ void buildThing(ref SaveGame game, Config config)
    }
    // scope(success) enforce(nodeFreeList.length == 0, "Still have deleted nodes");

    bool firstRun = true;
    if (nodeFreeList.length)
    firstRun = false;

    // Make chunks
    {
    enum chunkX0 = startChunk.x;
    @@ -133,17 +143,24 @@ void buildThing(ref SaveGame game, Config config)
    return index;
    }

    HashSet!NodePort portUsed;
    size_t[NodePort] portUsed;
    size_t numUsedPorts;
    void usePort(NodePort port)
    {
    // if (numUsedPorts == 2248) assert(false, "Here");
    assert(port !in portUsed, "Port used: %d".format(portUsed[port]));
    portUsed[port] = numUsedPorts++;
    }

    foreach (edge; game.edges)
    static foreach (i; 0 .. 2)
    portUsed.add(edge[i]);
    usePort(edge[i]);

    void addEdge(NodePort source, NodePort target, CableColor color = 0)
    {
    assert(source !in portUsed && target !in portUsed, "Port reused");
    usePort(source);
    usePort(target);
    game.edges ~= Edge(source, target, color);
    portUsed.add(source);
    portUsed.add(target);
    }

    void addRelays(NodePort source, NodePort target, CableColor color = 0)
  27. CyberShadow revised this gist May 27, 2024. 1 changed file with 21 additions and 18 deletions.
    39 changes: 21 additions & 18 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -43,24 +43,30 @@ void buildThing(ref SaveGame game, Config config)

    bool firstRun = true;

    static immutable nullNode = Node([0, 0, 0], [0, 0, 0], 0);

    // Delete nodes, edges, and objects in the build zone
    auto nodeDeleted = new bool[game.nodes.length];
    NodeIndex[] nodeFreeList;
    {
    auto nodeDeleted = new bool[game.nodes.length];
    foreach (i, ref node; game.nodes)
    if (node.pos[0] >= startWorld[0] && node.pos[0] < startWorld[0] + totalSize &&
    node.pos[1] == objectY &&
    node.pos[2] >= startWorld[2] && node.pos[2] < startWorld[2] + totalSize)
    {
    node = nullNode;
    nodeDeleted[i] = true;
    nodeFreeList ~= i;

    firstRun = false;
    }

    game.edges = game.edges.filter!(edge => !(nodeDeleted[edge[0][0]] || nodeDeleted[edge[1][0]])).array;
    game.relays = game.relays.filter!(o => !nodeDeleted[o.node]).array;
    game.edges = game.edges .filter!(edge => !(nodeDeleted[edge[0][0]] || nodeDeleted[edge[1][0]])).array;
    game.relays = game.relays .filter!(o => !nodeDeleted[o.node]).array;
    game.filters = game.filters.filter!(o => !nodeDeleted[o.node]).array;
    game.hubs = game.hubs.filter!(o => !nodeDeleted[o.node]).array;
    game.hubs = game.hubs .filter!(o => !nodeDeleted[o.node]).array;
    }
    //scope(success) enforce(!nodeDeleted.canFind(true), "Still have deleted nodes");
    // scope(success) enforce(nodeFreeList.length == 0, "Still have deleted nodes");

    // Make chunks
    {
    @@ -93,20 +99,17 @@ void buildThing(ref SaveGame game, Config config)

    NodeIndex addNode(WorldCoord pos, WorldCoord up = upVector, double angle = 0)
    {
    auto p = nodeDeleted.indexOf(true);
    if (p < 0)
    {
    NodeIndex index = game.nodes.length;
    game.nodes ~= Node(pos, up, angle);
    return index;
    }
    foreach (NodeIndex oldNodeIndex, ref oldNode; game.nodes)
    if (oldNode.pos == pos)
    assert(false, "Trying to create node on top of existing node");

    NodeIndex index;
    if (nodeFreeList.length)
    index = nodeFreeList.shift;
    else
    {
    NodeIndex index = p;
    game.nodes[p] = Node(pos, up, angle);
    nodeDeleted[p] = false;
    return index;
    }
    index = game.nodes.length++;
    game.nodes[index] = Node(pos, up, angle);
    return index;
    }

    NodeIndex addRelay(WorldCoord pos, WorldCoord up = upVector, double angle = 0, bool fixed = false, bool light = false)
  28. CyberShadow revised this gist May 27, 2024. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,3 @@
    import ae.sys.file;
    import ae.utils.array;
    import ae.utils.json;
    import ae.utils.meta;

    import std.algorithm.comparison;
    import std.algorithm.iteration;
    import std.algorithm.searching;
    @@ -13,6 +8,11 @@ import std.math.constants;
    import std.math.rounding;
    import std.path;

    import ae.sys.file;
    import ae.utils.array;
    import ae.utils.json;
    import ae.utils.meta;

    import config;
    import game;

  29. CyberShadow revised this gist May 27, 2024. 1 changed file with 5 additions and 6 deletions.
    11 changes: 5 additions & 6 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    import ae.sys.file;
    import ae.utils.array;
    import ae.utils.json;
    import ae.utils.meta;
    @@ -11,7 +12,6 @@ import std.math.algebraic;
    import std.math.constants;
    import std.math.rounding;
    import std.path;
    import std.stdio;

    import config;
    import game;
    @@ -453,10 +453,9 @@ void main()

    buildThing(game, config);

    game
    .toJson
    .toFile(
    "~/steam/.local/share/tunnet/slot_1.json"
    .expandTilde
    "~/steam/.local/share/tunnet/slot_1.json"
    .expandTilde
    .atomicWrite(
    game.toJson
    );
    }
  30. CyberShadow revised this gist May 27, 2024. 1 changed file with 8 additions and 6 deletions.
    14 changes: 8 additions & 6 deletions build.d
    Original file line number Diff line number Diff line change
    @@ -130,15 +130,17 @@ void buildThing(ref SaveGame game, Config config)
    return index;
    }

    HashSet!NodePort portUsed;
    foreach (edge; game.edges)
    static foreach (i; 0 .. 2)
    portUsed.add(edge[i]);

    void addEdge(NodePort source, NodePort target, CableColor color = 0)
    {
    assert(
    !game.edges.canFind!((Edge edge) =>
    [edge[0], edge[1]].any!((NodePort p) =>
    p.among(source, target)
    )
    ), "Port reused");
    assert(source !in portUsed && target !in portUsed, "Port reused");
    game.edges ~= Edge(source, target, color);
    portUsed.add(source);
    portUsed.add(target);
    }

    void addRelays(NodePort source, NodePort target, CableColor color = 0)