# WorldGraph A node-based procedural tile generation system. Graphs are composed of typed nodes wired together; evaluating the graph at a given world cell produces a tile ID. --- ## Overview World generation is expressed as a **directed acyclic graph (DAG)** of computation nodes. Each node takes zero or more typed inputs, performs some computation, and produces a single typed output. The graph is evaluated independently **per cell** — there is no shared mutable state between cells. The final result is an integer **tile ID**, obtained by calling `AsInt()` on whatever value the output node produces. --- ## Core Types (`WorldGraphTypes.h`) ### `Value` A tagged union that can hold an `Int`, `Float`, or `Bool`. Values are coerced automatically when a node requests a different type (e.g. `AsFloat()` on an Int just casts it). This means connections between different types are permissive at runtime, though the editor can flag them. ### `EvalContext` The per-cell context forwarded to every node during evaluation: | Field | Description | |---|---| | `worldX`, `worldY` | World-space tile coordinates of the cell being generated | | `seed` | World seed for deterministic noise | | `prevTiles` | Flat row-major array of tile IDs from the previous generation pass | | `prevOriginX/Y`, `prevWidth/Height` | Position and size of the previous pass buffer | `GetPrevTile(x, y)` safely reads the previous pass at any absolute world coordinate, returning `0` (AIR) for out-of-bounds or when no previous pass exists. --- ## Graph (`WorldGraph.h`) `Graph` owns a set of nodes and the connections between them. ``` NodeID AddNode(unique_ptr) — add a node, returns its ID bool Connect(from, to, inputSlot) — wire an output to an input Value Evaluate(outputNode, ctx) — evaluate for one cell bool IsValid(outputNode) — check all inputs are wired ``` **Cycle detection** runs at `Connect()` time. Any connection that would form a cycle is rejected and returns `false`. **Evaluation** is recursive and on-demand. Starting from the output node, the graph walks upstream, evaluating each dependency before the node that depends on it. Unconnected inputs receive a zero value of their declared type. --- ## Nodes (`WorldGraphNode.h`) All nodes derive from the abstract `Node` base class and implement: - `GetOutputType()` — the type this node produces - `GetInputTypes()` — the types of each input slot - `Evaluate(ctx, inputs)` — computes and returns the output value ### Source Nodes (no inputs) | Node | Output | Description | |---|---|---| | `ConstantNode` | Float | A fixed float value | | `IDNode` | Int | A fixed tile ID integer | | `PositionXNode` | Int | World X coordinate of the current cell | | `PositionYNode` | Int | World Y coordinate of the current cell | ### Math Nodes | Node | Inputs | Output | Description | |---|---|---|---| | `AddNode` | Float, Float | Float | a + b | | `SubtractNode` | Float, Float | Float | a − b | | `MultiplyNode` | Float, Float | Float | a × b | | `DivideNode` | Float, Float | Float | a / b (0 on divide-by-zero) | | `ModuloNode` | Float, Float | Float | fmod(a, b) | | `SinNode` | Float | Float | sin(a) in radians | | `CosNode` | Float | Float | cos(a) in radians | ### Comparison Nodes All produce `Bool`. Inputs are Float. `LessNode`, `GreaterNode`, `LessEqualNode`, `GreaterEqualNode`, `EqualNode` > **Note:** `EqualNode` compares floats directly — use only with integer-valued inputs. ### Boolean Logic Nodes | Node | Inputs | Output | |---|---|---| | `AndNode` | Bool, Bool | Bool | | `OrNode` | Bool, Bool | Bool | | `NotNode` | Bool | Bool | ### Control Flow `BranchNode` — selects between two values based on a condition: ``` inputs[0] Bool — condition inputs[1] Float — value if true inputs[2] Float — value if false ``` The concrete type of the chosen branch is passed through, so an `IDNode` connected to the true/false slot will produce an `Int` even though `GetOutputType()` reports `Float`. ### Previous-Pass Query Nodes These nodes read from the previous generation pass via `EvalContext`. They have **no inputs** — their parameters are baked into the node at construction time. The chunk generator uses these baked offsets to pre-compute how much border padding the previous pass must produce. | Node | Output | Description | |---|---|---| | `QueryTileNode(offsetX, offsetY, expectedID)` | Bool | `true` if the tile at `(worldX+offsetX, worldY+offsetY)` equals `expectedID` | | `QueryRangeNode(minX, minY, maxX, maxY, tileID)` | Int | Count of tiles matching `tileID` within the relative rectangle | | `QueryDistanceNode(tileID, maxDistance)` | Int | Chebyshev distance to the nearest tile matching `tileID`; returns `maxDistance + 1` if none found. The current cell is excluded. | --- ## Multi-Pass Chunk Generation (`WorldGraphChunk.h`) Complex terrain is built in **ordered passes**, where each pass can read the output of the previous one. For example: pass 1 places stone, pass 2 adds ores based on nearby stone. ### `GenerationPass` A thin struct pairing a `Graph` with the `NodeID` of its output node. ### Zero = "No Change" When a pass outputs `0` for a cell, it means **keep the previous pass's value** (or AIR if there was no previous pass). This lets later passes act as selective overrides rather than full rewrites. ### `GenerateRegion` Generates a single rectangular region using one graph. Walks every cell, builds an `EvalContext` pointing at `prevPassData`, evaluates the graph, and writes the result. ### `GenerateChunk` Runs a full sequence of passes over a chunk, automatically computing the padding each pass needs. ``` Algorithm: 1. The last pass generates exactly [chunkOriginX, chunkOriginY, W × H]. 2. Working backwards from the last pass, ComputeRequiredPadding inspects every QueryTile / QueryRange / QueryDistance node in each pass's graph to determine how much the previous pass's output region must expand. This expansion accumulates from last pass to first. 3. Passes execute in forward order. Each feeds its TileGrid output to the next pass as prevPassData. 4. The final TileGrid covers the original chunk region only. ``` ### `PaddingBounds` Records how many extra tiles are needed beyond the output region in each direction (`negX`, `posX`, `negY`, `posY`). Computed automatically by `ComputeRequiredPadding`, which walks the graph and unions the baked offsets from all query nodes. --- ## Serialization (`WorldGraphSerializer.h`) `GraphSerializer` converts a `Graph` to/from JSON (nlohmann/json). ```cpp nlohmann::json j = GraphSerializer::ToJson(graph); optional g = GraphSerializer::FromJson(j); bool ok = GraphSerializer::Save(graph, "path/to/file.json"); optional g2 = GraphSerializer::Load("path/to/file.json"); ``` JSON structure: ```json { "nextId": 5, "nodes": [ { "id": 1, "type": "Constant", "value": 0.5 }, { "id": 2, "type": "TileID", "tileId": 3 }, { "id": 3, "type": "Branch" } ], "connections": [ { "from": 1, "to": 3, "slot": 0 }, { "from": 2, "to": 3, "slot": 1 } ] } ``` Only `Constant` and `TileID` nodes carry extra fields. All query nodes (`QueryTile`, `QueryRange`, `QueryDistance`) serialize their baked offset/ID parameters. --- ## Minimal Usage Example ```cpp using namespace WorldGraph; // Build a graph that places tile 2 when Y < -10, otherwise tile 1 Graph g; auto posY = g.AddNode(std::make_unique()); auto thresh = g.AddNode(std::make_unique(-10.0f)); auto cond = g.AddNode(std::make_unique()); auto tileA = g.AddNode(std::make_unique(2)); // underground tile auto tileB = g.AddNode(std::make_unique(1)); // surface tile auto branch = g.AddNode(std::make_unique()); g.Connect(posY, cond, 0); // posY → Less.a g.Connect(thresh, cond, 1); // -10 → Less.b g.Connect(cond, branch, 0); // bool → Branch.condition g.Connect(tileA, branch, 1); // tile2 → Branch.true g.Connect(tileB, branch, 2); // tile1 → Branch.false // Generate a 64×64 chunk at world position (0, -32) GenerationPass pass { g, branch }; TileGrid chunk = GenerateChunk({ pass }, 0, -32, 64, 64, /*seed=*/12345); ```