# Reference

# Map Rules

This document is the **authoritative specification of the game world's data model and rules**. It is the ground truth for how maps are stored, how blocks behave, and how gameplay (pathfinding, movement, editing, generation) derives from that data.

It binds **humans and AI agents alike**. If code, docs, or habits contradict this document, this document wins. If a rule genuinely needs to change, change it here first, then update the code — never the reverse.

Implementation status is marked per section (`[built]`, `[partial]`, `[planned]`) so readers know what exists today vs what the rules call for.

---

### 1. Purpose &amp; scope

The model must serve these goals, all from **one** data structure:

- **Final Fantasy Tactics maps, 1:1** — integer-height terrain, slopes, steps, thin (.2) blocks, water, bridges.
- **Space-Engineers-style building** — a broad set of block shapes, placed/rotated/removed by an editor, with arbitrary fractional heights.
- **Rolling hills of any height** — continuous terrain surfaces, not grid-quantized.
- **Dynamic terrain** — explosions and moves can carve holes in walls and terrain.
- **Procedural generation** — deterministic from a seed: hills, cliffs, rocks, buildings; fixed-size, round, and open-world maps.
- **An in-game map editor** — place / rotate / delete blocks.

`[built: chunked voxel storage, codec, box + smooth renderers]` — the rest of the rules describe the full model; parts marked planned are not yet implemented.

---

### 2. Guiding principles

1. **The data layer is the ground truth.** Every block's exact size and shape lives in the map data. Rendering never modifies it.
2. **Render never feeds back.** Smoothing, shading, props, and cosmetics are derived in the render layer and can never influence rules or map data.
3. **One structure.** A map is a single voxel grid. "Terrain" and "blocks" are *conceptual* roles of voxels, not separate data structures.
4. **No derived storage.** Anything derivable from the voxel grid (column tops, surfaces, adjacency) is computed or cached, never stored as map data.
5. **Deterministic.** Everything derived from the map + seed (generation, cosmetics) reproduces identically given the same inputs.
6. **Append-only catalog.** Tile types are added, never reordered or removed (maps reference them by index).
7. **Extensible shapes.** A block shape is an enum value plus geometry. Adding a shape is additive and safe.
8. **The map stores facts; rules decide.** Walkability, movement cost, hazard damage, and LoS are *judgments* the rules layer derives from the data — never stored in the map.

---

### 3. The voxel model

#### 3.1 Storage

- The map is a **3D grid of 1×1×1 cells**.
- Cells are grouped into **chunks of 16³** (`VoxelMap.CHUNK_SIZE`), keyed by chunk coordinate `Vector3i`.
- Fixed maps occupy a bounded set of chunks (an optional `bounds` AABB); open-world maps are unbounded and generate chunks on demand.
- `[built]` — `src/map/voxel_map.gd`.

#### 3.2 Voxel encoding

Each cell holds at most **one block**. A cell is either empty or a block:

```
empty                      0
block  { type, shape, rot, height }

```

Packed as 3 bytes little-endian: - byte 0 — `type + 1` (1-indexed; `0` = empty, so catalog index 0 never collides with "empty") - byte 1 — `shape` (4 bits) | `rot` (4 bits) - byte 2 — `height` (0..255 → fraction 0..~1.0, 1/256 resolution); `0` means "use the shape default"

`[partial]` — the 2-byte form (type/shape/rot) is built; the `height` byte is **planned** (schema 4).

#### 3.3 Height = continuous surfaces

**`height` is the fraction of its cell that a block occupies**, measured from the block's anchored edge:

- `FULL` occupies the whole cell.
- `HALF` is a block anchored at the bottom, occupying `height` of the cell (default 0.5).
- `SLAB` is a block anchored at the top, occupying `height` of the cell (default 0.2).

A column's **surface height** is `cell_y + height` — a continuous float. Terrain hills therefore rise smoothly (0.8, 0.9, 1.0, 1.3, 1.7…) with no quantization. `[planned]`.

---

### 4. Materials &amp; tile types

A tile type (catalog entry) is the shared identity of a **material**. It describes *what the material is* — not *how the world treats it*.

**Material facts (ground truth):**

<table class="bs-table" id="bkmrk-field-meaning-id-sta"><thead><tr><th>field</th><th>meaning</th></tr></thead><tbody><tr><td>`id`</td><td>stable name (`StringName`)</td></tr><tr><td>`solid`</td><td>physically occupies its volume</td></tr><tr><td>`fluid`</td><td>physically non-solid; can flow later (water)</td></tr><tr><td>`shape`</td><td>canonical block shape (voxels may override per cell)</td></tr><tr><td>`height`</td><td>canonical fractional height (voxels may override)</td></tr></tbody></table>

**Rules-layer properties (NOT ground truth — the rules engine owns these):**

`walkable`, `move_cost`, `hazard` / `hazard_damage`, and the behavioral flags (`BLOCKS_LOS`, `IS_PLATFORM`, `BLOCKS_JUMP`, `REQUIRES_FLIGHT`). These are *judgments* a rules layer consults by material. They may currently live on `TileType` for convenience, but they are **rules data, never map data** — the map never carries them.

**Contract:** the catalog is **append-only by index**. Maps store type indices; never reorder or remove entries. `[built, partial]` — facts live on `TileType`; the rules-layer split is built (`src/rules/material_rules.gd` owns passability/cost/hazard/flags); per-type `height` is still planned.

---

### 5. Block shapes

The shape enum defines the geometry of a block within its 1×1×1 cell. **Every shape must be a closed volume** (no open faces) so it renders cleanly.

<table class="bs-table" id="bkmrk-shape-volume-full-wh"><thead><tr><th>shape</th><th>volume</th></tr></thead><tbody><tr><td>`FULL`</td><td>whole cell</td></tr><tr><td>`HALF`</td><td>bottom block of `height`</td></tr><tr><td>`SLAB`</td><td>top slab of `height`</td></tr><tr><td>`SLOPE`</td><td>full-length ramp (+x)</td></tr><tr><td>`HALF_SLOPE`</td><td>half-length ramp</td></tr><tr><td>`STAIR`</td><td>stepped block</td></tr><tr><td>`WEDGE`</td><td>corner wedge (closed)</td></tr><tr><td>`CORNER`</td><td>L-shaped inner corner</td></tr></tbody></table>

- **Rotation** in 90° steps (`rot` 0–3 meaningful; geometry treats `rot % 4`).
- Directional shapes (`SLOPE`, `HALF_SLOPE`, `STAIR`, `WEDGE`, `CORNER`) rotate around Y.
- The shape gallery map (`shape_gallery.vmap`, planned) shows every shape in isolation and is the visual reference.

`[built: FULL/HALF/SLAB/SLOPE/WEDGE]` — HALF\_SLOPE/STAIR/CORNER and the closed WEDGE are planned. `src/render/shape_geometry.gd`.

---

### 6. Terrain

Terrain is **voxel columns**: a run of solid voxels rising from the map base (`y=0`) to a column top. The top voxel carries the fractional `height`.

- **Rolling hills** = columns whose tops vary continuously (`surface = y + height`).
- **Cliffs/steps** = adjacent columns whose tops differ.
- **Slopes** = a surface that ramps between adjacent column tops (FFT-style), *or* a `SLOPE` block where a discrete slope tile is wanted.
- **The base** is implicitly `y=0`; generators fill solid voxels from the base up.

`[partial]` — column filling exists via FULL/HALF stacks; continuous `height` tops are planned.

---

### 7. Blocks &amp; placement

Blocks are voxels placed by the editor, generator, or gameplay:

- Blocks **snap to cells** (integer grid). A block occupies `[cell_y, cell_y + height)`.
- **Blocks replace terrain.** Placing a block clears any terrain voxels under its footprint; removing it restores terrain (generator- or map-provided). This is the Space-Engineers rule.
- A **building's footprint** may flatten sloped terrain under it (placement levels the columns it touches) — chosen per-building by the placement rule.
- Buildings always rest on integer cell bounds; they never "stack on" a fractional block in the same column.

`[planned]` — no placement engine yet.

---

### 8. Surfaces, adjacency &amp; movement

These are the rules rules-engine and pathfinding will consume. All are derived from the voxel grid, never stored. The engine receives **only facts** — surface heights, block shapes, materials, fluid state, adjacency — and **produces the verdicts** (walkable? cost? blocked?). Verdicts are never written back into the map.

#### 8.1 Surfaces

A **surface** is a place a unit can stand: - **Column top:** `surface(x, z) = y + height` of the top voxel in column `(x, z)` (terrain). - **Block top:** the top face of any solid block (stand on a roof, a bridge, a wall cap).

`surface(x, z)` returns `{ height: float, type }`. `[planned]`.

#### 8.2 Adjacency

Rules query neighbors directly: `voxel_at(x, y, z)` plus its 6 (or 26) neighbors — a plain grid read. Anything about an adjacent block (its shape, height, type, fluid state) is available; the rules decide what it *means*.

#### 8.3 Movement thresholds (current rule)

Movement cost between adjacent columns compares their surface heights. **Rise** = `surface(neighbor) - surface(current)`. The current thresholds:

- **Rise ≤ 0.25** — free step onto a flat surface (or up a very low lip).
- **0.25 &lt; rise ≤ 1.0** — needs a supporting slope/step (the approach side has a `SLOPE`/`STAIR`/partial block, or the columns are separated by a ramp surface), or costs extra move.
- **Rise &gt; 1.0** — blocked, unless the actor has a jump/lift ability that explicitly allows it.

These numbers are a **deliberate, tunable rule** — they live in one place (the rules module) and must not be scattered. `[built: src/rules/movement_rules.gd]`.

#### 8.4 Pathfinding

Pathfinding runs over walkable surfaces: - Nodes = `(x, z, surface)` (a column top, or a block top). - Edges = adjacent surfaces within the movement thresholds (8.3). - Blocked by: non-walkable surfaces, `solid` blocks between surfaces, hazards that forbid entry. - `surface()` results are cached per map and invalidated only where edited.

Built as a **budgeted 4-directional Dijkstra** over the surface graph: `PathEvaluator` (`src/pathfinding/path_evaluator.gd`) composes terrain rules (passability/cost), climb rules (`MovementRules`), and unit mobility (`UnitProfile` traits/statuses — fly, float, swim, slow/stop) into per-step verdicts; `Pathfinder` (`src/pathfinding/pathfinder.gd`) returns either the lowest-cost path to a destination (player move, NPC) or the full reachability bubble (the FFT movement range), and reports every hazardous cell crossed so movement/combat can apply the rule's damage and statuses. Surfaces currently derive from block shape geometry (schema 3); the `height` byte (schema 4) will refine them. `[built: src/pathfinding/*, src/rules/*]`.

---

### 9. Fluids

- Water (and future fluids) is a **non-solid `fluid` voxel type** (`solid = false`, `fluid = true`).
- Pools are clusters of fluid voxels filling low columns.
- Fluid flow simulation is **future**; the data contract only requires a fluid voxel with a type and height. `[built: water type is fluid]`.

---

### 10. Dynamic edits

Any code may read or modify voxels:

- `set_voxel(x, y, z, block)` / `clear_voxel(x, y, z)` — the editor, abilities, and explosions all go through these.
- **Explosions** clear a voxel sphere (walls *and* terrain), carving craters.
- **Surface-cache invalidation:** editing a column invalidates that column's cached `surface()`.
- Edits are never "render changes" — they are data changes that the renderer reflects.

`[built: set/clear; planned: cache invalidation]`.

---

### 11. Procedural generation contract

Generators write ordinary voxels; they are subject to the same rules.

- **Deterministic** from `(seed, position)`. Same seed → same map, everywhere, always.
- **Terrain:** per-column height from noise → fill columns from the base (rule 6).
- **Structures:** stamp voxel shapes (buildings, trees, rocks — those occupying voxel space) on top (rule 7).
- **Map shapes:** square = bounded chunk set; **round** = square bounds with a circular playable mask (columns outside the circle stay empty); **open world** = unbounded, chunks generated on demand from `(seed, chunk_coord)`.
- Generators must produce *valid* maps (rule 12).

`[planned]`.

---

### 12. Codec &amp; validation

- Binary schema versioned in the header; loaders reject mismatches loudly.
- A map is **invalid** if: a voxel's type index exceeds the catalog, `shape` is unknown, `rot` ≥ 8, or chunk data is malformed. Loaders must reject invalid maps, never silently repair them.
- The voxel grid is the only serialized form. `[built]`.

---

### 13. Rendering discipline

Two views exist; both are derived from the same data:

- **RAW** — the map data literally: every block at its exact shape/height, flat per-type colors. This is the truth view; rules are debugged against it.
- **RENDER** — cosmetics only: the **exact same voxel shape geometry** as the debug view, plus a seeded per-cell tint, rocks/foliage props, day/night, and the top-grid shader. **Effects never change geometry** — slopes stay slopes, voids stay holes; only colors and props are added.

`[built: debug + render views — identical shape geometry, render adds tint + props]`.

---

### 14. Rules for agents

Non-negotiable conventions for anyone (human or AI) working on this project:

1. **Never store derived data in the map.** Surfaces, adjacency, LoS, cached geometry — computed or cached, never serialized.
2. **Never let rendering feed back into logic.** No rule may depend on what the render layer does.
3. **The catalog is append-only.** Add types; never reorder or remove.
4. **Movement/surface rules live in one place.** Thresholds (§8.3) are a single tunable module, not scattered constants.
5. **Follow this document.** When a rule changes, change `MAP_RULES.md` first, then the code, then mark the code section `[built]`.
6. **Validation rejects, never repairs.** Corrupt maps fail loudly.
7. **The map is facts, not rules.** Never encode walkability or behavior verdicts into map data; the rules layer owns all judgments.
8. **Simulation is deterministic.** No engine RNG (`randi`, `randomize`) in any simulation path — use `DeterministicRng`, seeded from the match. Sim math stays in integers and exact fractions.
9. **Rule data is versioned.** `RulesetVersion` hashes the catalog + material rules + movement rules; a match pins it, and clients that don't match refuse to join.
10. **State changes are commits.** Every mutation — including mid-combat map edits — flows through the sequence-numbered commit log. Nothing mutates simulation state out-of-band.

---

*Revision log:* created as the foundation spec. Supersedes informal conventions discussed before it existed. Added the facts-vs-rules principle (§2.8, §14.7) and built the rules layer (`MaterialRules`/`MovementRules`/`UnitProfile`) plus the pathfinding pipeline (`PathEvaluator`/`Pathfinder`) per §8. Added the determinism contract (§14.8-14.10), the deterministic RNG, ruleset versioning, and the commit log (multiplayer foundation); combat is specified in `COMBAT.md`.

# Project Context

Project knowledge. Agent-facing; read this before any work. Not the doc for humans.

### Overview

A Final Fantasy Tactics-like tactical RPG (grid-based, turn-based combat with height, movement, and class/job systems) built in Godot 4.x with GDScript. **Codename: `operation_tactics`** — the real name is TBD. Nothing about the codename should leak into user-facing strings.

### Goals

- Faithful FFT-style tactical combat: isometric(ish) grid, move/act/end-turn flow, terrain height, facing, and line-of-sight.
- Job/class system with learning abilities from action (JP), not just leveling.
- Branching/unique characters, story-driven campaign.
- One cohesive game, not a tech demo — a single unified codebase.

### Current State

- Engine files exist (project.godot, directory skeleton). Map data core (registry, schema, binary codec), the 3D heightfield renderer (`MapView`/`MapTheme`), the seeded cosmetic layer (`MapDetails`), a demo map asset, and the custom test pipeline are in place. `main.tscn` boots the demo map with an FFT-style isometric camera (WASD pan / QE rotate / wheel zoom) and an **astronomical day-night cycle** (`src/render/day_night.gd`): daylight length computed from latitude + month (Virginia in August ≈ 13.5h), a sun that only lights above the horizon, a phase-driven moon with cloud-dimming, and clear-sky starlight. Tune `latitude_deg`/`month`/`day`/`cloud_cover`/`moon_phase`/`cycle_seconds` on the Sun node. **F2** toggles a free-fly spectator camera (`src/camera/free_cam.gd` — right-mouse look, WASD/QE move, wheel = speed, F3 recenters); it never touches the FFT camera's state, so toggling back is seamless.
- **Rules layer + pathfinding pipeline built** — facts-vs-rules split is done (`TileType` = facts; `MaterialRules`/`MovementRules`/`UnitProfile` = judgments) with a budgeted Dijkstra pathfinder (`Pathfinder`/`PathEvaluator`) providing FFT movement-bubble reachability, lowest-cost paths, hazard reports, and fly/float/swim/status support. See "Rules &amp; Pathfinding pipeline".
- **Combat designed; multiplayer foundation built** — `COMBAT.md` is the authoritative battle spec (deterministic CT turn order, streamlined stats, the eight elements, living-map mutations as commits). Foundation primitives exist: `Elements`, `DeterministicRng`, `RulesetVersion`, `Commit`/`CommitLog` (with the commit vocabulary), and streamlined combat stats + affinities on `UnitProfile`. See "Combat &amp; multiplayer foundation".
- **Procedural arena** — `voxel_arena.vmap` is the seeded showcase/test map: rolling noise terrain + stamped features covering all 10 materials, all 5 shapes, rotations 0-3, hazards, real void, multi-level climbing, and a pathable plaza. Generated by `src/map/arena_generator.gd` (`ArenaGenerator`), written by `tools/build_voxel_arena.tscn`, validated by `tests/map/voxel_arena_test.gd` (determinism + asset-in-sync + feature coverage). Now the default map in `main.gd`. Test suite is 117 green.
- Repository created on Forgejo and pushed. Repo uses the **SHA-256 object format** — never recreate it as sha1, and never rely on push-to-create for this repo (Forgejo's push-to-create ignores object format and would make a sha1 repo).

### Engineering Standards

Applies to every agent and human working here. Goal: a codebase humans can maintain long-term, not one that merely "works."

#### Code quality

- **Human-first.** Idiomatic GDScript, clear names, logical file layout. Readability first, efficiency second — but never accept needless waste (hot paths: avoid per-frame allocations).
- **Concise.** Smallest correct change. No speculative generality, no backward-compat shims without a concrete need.
- **Reuse over copy.** Shared frameworks (tile registry, map codec; later combat math, pathfinding) live once and are used everywhere. Rule of three: third copy → extract.
- **Modular &amp; layered.** Scripts stay focused (one responsibility); layers point one way — data/logic must not import rendering; cross-cutting concerns use signals. No spaghetti, no circular references.
- **Debuggable.** Keep logic in deterministic functions (pure input → output) where practical; avoid hidden global mutation.

#### Comment policy

- Comment the WHY and the intent: design decisions, non-obvious data formats, invariants, cross-module contracts. A reviewer should understand your intent.
- Never narrate WHAT the code does; no AI-style filler (e.g. `# get the player position`). No emoji. Code should look hand-written.

#### Testing (mandatory, Factorio-style)

- Before every commit: `./run_tests.sh` runs the full suite headless and must pass; a pre-commit hook enforces it. Never commit red.
- Suite covers: codec round-trip (byte-exact), registry append-only guard, and every subsystem's core behavior as it lands.
- Benchmark mode (`--bench`, planned) for perf-sensitive paths (combat sim, map load); checked for regressions, not just pass/fail.
- Test layout: `res://tests/**/*_test.gd`, custom runner (see `res://tests/run_tests.gd`). Tests extend the `TestCase` base and use its `eq/is_true/...` helpers.

#### Growing-project guardrails

- **Format/version drift** → schema version in every binary format; loaders reject mismatches loudly, never silently.
- **Duplication creep** → rule of three + review.
- **Scope creep** → anything new must fit the modular layout or the plan changes (and this doc gets updated).
- **Dead code** → no "just in case" branches; keep the tree lean.
- **Conventions drift** → this doc is the arbiter; update it when rules change.
- **Single-file bloat** → split when a script exceeds ~300 lines or has more than one job.

### Map Data Architecture

**`MAP_RULES.md` is the authoritative world-model spec** — read it before touching anything map-related. It defines the voxel model, tile types, shapes, terrain/blocks, movement thresholds, fluids, dynamic edits, generation, validation, and the agent rules. This section is the quick-reference; MAP\_RULES.md wins on any conflict.

**Direction: voxel pivot.** **`VoxelMap`** (`src/map/voxel_map.gd`): a chunked 16³ grid of 1×1×1 voxels (open-world ready; columns are subsumed as vertical runs of solid voxels). Each voxel is `{type, shape, rot}` (2 bytes packed; `TileType.Shape` = FULL/HALF/SLAB/SLOPE/WEDGE…, per-voxel shape + rotation for the editor). Serialized by **`VoxelCodec`** (`voxel_codec.gd`, schema 3, `.vmap` files). **`VoxelMesh`** (`src/render/voxel_mesh.gd`) builds the combined shape mesh with face culling (interior faces shared with a full solid neighbor are dropped); **`VoxelView`** renders it. `main.gd` defaults to the voxel demo (`assets/maps/voxel_demo.vmap`, built by `tools/build_voxel_demo.tscn`) and loads `.vmap` as voxels vs `.otmap` as the legacy column model (still present until the column code is retired). Cosmetic layer, hover/select and picking are column-only for now.

Global design (see `res://src/map/` for implementation). Maps are **2D grids of columns**: each cell owns its entire z column, so interior-under-roof and bridges are expressible as stacked walkable surfaces.

- **`TileType`** (`tile_type.gd`) — one shared entry per tile kind: **material facts only** (`id`, `block_height`, `shape`, `solid`, `fluid`). Behavior (walkability, cost, hazards, flags) lives in the rules layer (`MaterialRules`), never here. **Logic only** — visuals live in a separate theme table (`type_id → scene/texture`).
- **`TileTypeRegistry`** (`tile_type_registry.gd`) — the **global catalog**. **Append-only**: never reorder/delete entries, maps reference types by index, so adding types is always safe. `version` guards against breaking changes; loaders reject `catalog_version > registry.version`.
- **`MapData`** (`map_data.gd`) — size, catalog\_version, spawn points, and a column per cell: `{ elevation, ground (type index), layers: [{z, type, rot}] }`.
- **`MapCodec`** (`map_codec.gd`) — `StreamPeerBuffer` (little-endian) ↔ `PackedByteArray`. Binary layout is the contract (see file header): magic `OTMP` + schema version + catalog version + dims + spawns + per-cell: flags u8, elevation u16, ground u16, optional layer list. Map files use the **`.otmap`** extension.
- **`MapPicker`** (`src/map/map_picker.gd`) — resolves a cursor ray to the **specific block** hit (cell + surface height), not just the column: DDA over the grid + ray-vs-AABB on every block. View-aware (RENDER fills/ thin layers vs RAW data heights). `main.gd` feeds `hover`/`selected` cell+height into the top-grid shader, so a click on a roof highlights the roof, a click on a cliff face highlights that block.
- **`MapTheme`** (`src/render/map_theme.gd`) — visual theme, `type_id → color` (later scene/texture). Kept out of `TileType` so map data stays renderer-agnostic; swapping the theme restyles the whole game.
- **`MapView`** (`src/render/map_view.gd`) — the single data→rendering bridge: one combined `ArrayMesh`. Two view modes (`MapView.ViewMode`, toggled with **F1** in `main.gd`): **RENDER** = the game look (terrain fills to the map base — the **bedrock rule**, no terrain voids; layers drawn thin; cosmetic layer on; no wireframe) and **RAW** = the map data literally (ground blocks at `[elevation, elevation + block_height]` with voids intact, layers at full data height, flat colors + wireframe, no cosmetics) — the geometry rules/pathfinding are written against the RAW view. Renderer direction is **3D heightfield**. The RENDER top grid is a **fragment shader** (`src/render/top_grid.gdshader`) on a duplicate of the terrain mesh: + crosses at lattice corners + dashed edges, uniform and anti-aliased, tinted per `hover_cell`/`selected_cell` uniforms. `main.gd` picks the cell under the cursor (hover on mouse move, blue select on left-click) and feeds the uniforms.
- **`MapDetails`** (`src/render/map_details.gd`) — the **cosmetic layer** (two-layer model): a pure deterministic function of `(map, seed)` producing per-cell tint variance, C0-continuous surface jitter (no seams), and scattered props (rocks/bushes/tufts via `MultiMesh`). Purely visual — never feeds back into map data or rules.
- **Two-layer model + seed contract** — `MapData.seed` is part of the map (codec field, schema 2). Ground truth is the column blocks (drives rules/pathfinding later); the cosmetic layer is derived from `(map, seed)` and regenerated freely — same seed, same result, everywhere. Determinism rules: integer-hash noise keyed by `(seed, x, y, z)`, fixed iteration order, no engine RNG. The seed can later drive other derived content (spawns, variants).
- **`DemoRegistry`** (`src/map/demo_registry.gd`) — canonical demo catalog; stand-in for a future on-disk catalog file (append-only by index). The demo map asset (`res://assets/maps/demo.otmap`, seed 12345) is generated by `tools/build_demo_map.tscn`.
- **`TestRegistry`** (`src/map/test_registry.gd`) + **test map** (`assets/maps/test_map.otmap`, 53×27, seed 20260815) — the scenario museum: every hazard, elevation shape, layered structure, wall/corridor, rotation, and spawn surface the model can express, laid out in 8×6 zones (see `tools/build_test_map.gd` — its coordinates are the contract; `tests/map/test_map_test.gd` spot-checks each zone). View it with `godot --path . -- --map=res://assets/maps/test_map.otmap`.
- **Procedural maps** — a generator is just another producer of `MapData` (same shape as the demo builder); it flows through the same codec and renderer. Generators must produce valid `MapData` (indices within the registry, in-bounds elevations/layers).
- **In-game map editor** (planned) — mutates in-memory `MapData` and re-packs through the same codec; palette is the registry. Editor stays an orthogonal consumer of the schema.

#### Rules &amp; Pathfinding pipeline

- **`MaterialRules`** (`src/rules/material_rules.gd`) — the rules layer: how the world *treats* each material. One `TerrainRule` per type id: `passable`, `move_cost`, `hazard`/`hazard_damage`, behavioral `flags`, `status_on_enter`. `for_registry()` yields the rule set for a catalog (defaults + per-id overrides — the single behavioral source for demo/test ids). All editor-editable as Resources.
- **`MovementRules`** (`src/rules/movement_rules.gd`) — the single tunable module for MAP\_RULES §8.3 thresholds: `free_step` (0.25), `step_ceiling` (1.0), `climb_cost_per_step`, `water_cost_extra`.
- **`UnitProfile`** (`src/rules/unit_profile.gd`) — unit mobility: `move_budget`, traits (FLOAT/FLY/SWIM), status effects (slow/stop/immobilize modify the budget; float/swim statuses grant traits).
- **`PathEvaluator`** (`src/pathfinding/path_evaluator.gd`) — composes map facts (column surfaces from voxel shape geometry) × rules × unit state into per-step verdicts (`step_cost`, `hazard_at`).
- **`Pathfinder`** (`src/pathfinding/pathfinder.gd`) — budgeted 4-directional Dijkstra over the surface graph. `find_path()` → lowest-cost path to a destination; `reachability()` → the FFT movement bubble (cost field + parents). `PathResult` carries the ordered path, total cost, and every hazardous cell crossed (movement/combat applies the rule's damage/status as the unit traverses it).
- Pathfinding operates on `(x, y, surface)` nodes derived from the voxel grid; inter-surface transitions (stairs, ledges) are edge costs per MAP\_RULES §8.3. Surface heights come from shape geometry until the schema-4 `height` byte lands.

#### Combat &amp; multiplayer foundation

**`COMBAT.md` is the authoritative battle spec** — read it before touching combat. It locks the FFT baseline (turn order, turn flow, occupancy, targeting) and defines our divergences: deterministic CT scheduling, the eight-element system with affinities, and the living map (destruction/placement/field events/collapse, all as commits).

- **`Elements`** (`src/rules/elements.gd`) — the eight elements (fire/ice/lightning/water/wind/earth/holy/dark) + affinity helpers (`effective_multiplier`, `standing_multiplier`).
- **`UnitProfile`** (`src/rules/unit_profile.gd`) — now carries the streamlined stats (hp/mp/atk/def/mag/mdf/spd) and per-element `affinities` alongside mobility.
- **`DeterministicRng`** (`src/rules/deterministic_rng.gd`) — SplitMix64-seeded PRNG; the ONLY randomness source allowed in simulation paths (COMBAT.md §7.3, MAP\_RULES §14.8).
- **`RulesetVersion`** (`src/rules/ruleset_version.gd`) — FNV-1a hash of catalog + material rules + movement rules; a match pins it, mismatched clients refuse to join.
- **`Commit`/`CommitLog`** (`src/net/`) — the append-only, sequence-numbered state-change stream (the shared clock of a match). The kind vocabulary (move\_unit, set\_block, remove\_block, apply\_damage, …) is COMBAT.md §10.1, reflected as `Commit.KIND_*` constants.
- Turn controller, damage math, targeting, field effects, collapse, and the snapshot codec: `[planned]` (specified in COMBAT.md).

### Conventions

- Godot 4.x GDScript; follow the `godot4` skill (theme/font override quirks, ConfigFile gotchas, explicit type annotations — type warnings treated as errors). Comments explain intent, not narration (see Engineering Standards).
- snake\_case for scripts/vars, PascalCase for node names.
- Branch `main`; commits concise, imperative mood, lowercase, focused on "why"; push frequently.

### Docs &amp; Wiki

- **Canonical docs live in the repo**: `GAME_BIBLE.md` (the game bible — vision, systems, pipelines, baselines, glossary; read it first), `MAP_RULES.md` (authoritative world-model spec), `COMBAT.md` (authoritative battle spec), and this file.
- **Mirrored to the wiki** (`https://wiki.fifthdread.com/books/operation-tactics`) by `tools/sync_wiki.py` — the wiki is a rendered view, the repo is the source of truth.
- Sync (idempotent; creates missing pages, updates existing): `source ~/opencode/api_secrets.sh && python3 tools/sync_wiki.py`. `--check` previews without writing. Which docs map to which pages is declared in `tools/wiki_manifest.json`.

### Shipping

Shipping = **commit and push** to the project repo.

- Remote: Forgejo — `ssh://git@forgejo.fifthdread.com:223/Fifthdread/operation_tactics.git`. **SHA-256 object format** — the repo must be pre-created on the server as sha256; push-to-create will not work for this repo.
- If the game is ready for distribution on Arch-based Linux: also update the **PKGBUILD** so `paru -S <pkg>` installs it.
- PKGBUILD lives in the `Fifthdread/pkgbuilds` repo at `~/opencode/pkgbuilds/` (one dir per package, `-git` variant recommended for an in-development game).
- Workflow: bump `pkgver=` (`rev-count.commit`), regenerate `makepkg --printsrcinfo > <pkg>/.SRCINFO`, commit `.SRCINFO` alongside, push. Full detail in the pkgbuilds project's CONTEXT.md/AGENTS.md.