# Hex Direction Conventions — Rust vs Design-App Canvas This document is the authoritative reference for translating Rust WASM direction indices to the design-app flat-top canvas direction indices. Any TypeScript file that consumes `flow_out`, `boundary_dir`, or any other Rust-emitted direction value must route through `rustDirToFlatTopDir` from `hexCanvas.ts` rather than hard-coding its own table. --- ## 1. Rust Convention (mc-core/algorithms/hex.rs) **Coordinate system:** axial `(q, r)` with `s = -q - r` (cube constraint). **Storage grid:** odd-q offset, where `col = q` and `row = r + (q − (q & 1)) / 2`. Odd columns are shifted **up** in axial row relative to even columns. **Direction indices `0–5`** (`AXIAL_DIRECTIONS`): | Index | Name | Axial delta `(dq, dr)` | |-------|------|------------------------| | 0 | E | (+1, 0) | | 1 | NE | (+1, −1) | | 2 | NW | ( 0, −1) | | 3 | W | (−1, 0) | | 4 | SW | (−1, +1) | | 5 | SE | ( 0, +1) | These labels (E/W as neighbours) are the **pointy-top** convention. They match `HexUtils.AXIAL_DIRECTIONS` in GDScript — no translation needed on the Godot side. **`ODD_Q_NEIGHBORS`** gives the same neighbours expressed as offset `(dcol, drow)`: | Parity | E | NE | NW | W | SW | SE | |--------|---|----|----|---|----|----| | even col | (+1, 0) | (+1, −1) | (0, −1) | (−1, −1) | (−1, 0) | (0, +1) | | odd col | (+1, +1) | (+1, 0) | (0, −1) | (−1, 0) | (−1, +1) | (0, +1) | Note the parity dependence: W is `(−1, −1)` from an even column but `(−1, 0)` from an odd column. This is the fundamental source of parity bugs. --- ## 2. Design-App Canvas Convention (hexCanvas.ts) **Orientation:** flat-top. Corners are placed at angles 0°, 60°, 120°, 180°, 240°, 300° (E and W positions are *corners*, not edge midpoints). **Pixel layout:** `hexToPixel` places odd columns shifted **down** by `h/2` relative to even columns (where `h = size * √3`). **`hexCorners` corner indices** (clockwise from E): | Index | Position | |-------|----------| | 0 | E | | 1 | SE | | 2 | SW | | 3 | W | | 4 | NW | | 5 | NE | **`EDGE_CORNERS` — six edges** (flat-top has no E or W *edges*): | Index | Name | Corner pair | |-------|------|-------------| | 0 | NE | 5→0 (NE→E) | | 1 | SE | 0→1 (E→SE) | | 2 | S | 1→2 (SE→SW) | | 3 | SW | 2→3 (SW→W) | | 4 | NW | 3→4 (W→NW) | | 5 | N | 4→5 (NW→NE) | **`neighborCoords(col, row)` — neighbor offsets:** | Index | Name | Even col `(dcol, drow)` | Odd col `(dcol, drow)` | |-------|------|-------------------------|------------------------| | 0 | NE | (+1, −1) | (+1, 0) | | 1 | SE | (+1, 0) | (+1, +1) | | 2 | S | (0, +1) | (0, +1) | | 3 | SW | (−1, 0) | (−1, +1) | | 4 | NW | (−1, −1) | (−1, 0) | | 5 | N | (0, −1) | (0, −1) | Opposite directions: 0↔3 (NE↔SW), 1↔4 (SE↔NW), 2↔5 (S↔N). --- ## 3. Translation Table — Rust → Flat-Top The translation is **parity-independent**: the axial→offset→flat-top path produces the same flat-top index for both even and odd columns. | Rust dir | Rust name | Flat-top dir | Flat-top name | |----------|-----------|--------------|---------------| | 0 | E | 1 | SE | | 1 | NE | 0 | NE | | 2 | NW | 5 | N | | 3 | W | 4 | NW | | 4 | SW | 3 | SW | | 5 | SE | 2 | S | **Why parity-independent?** The parity shift in Rust's odd-q convention moves odd-col axial rows in the *same direction* as the flat-top canvas's odd-col pixel shift. The asymmetry cancels: the physical hex each Rust direction points to is always the same flat-top named direction, regardless of which column you start in. --- ## 4. Worked Example — `flow_out = 1` (Rust NE) Scenario: tile at `(col=4, row=3)` (even column). `flow_out = 1` → Rust NE. 1. Look up translation: Rust 1 (NE) → Flat-top 0 (NE). 2. `neighborCoords(4, 3)[0]` = `(4+1, 3−1)` = `(5, 2)`. 3. The flow arrow points from `(4,3)` toward the center of tile `(5, 2)`. Same tile at `(col=5, row=3)` (odd column). `flow_out = 1` → still Flat-top 0 (NE). 4. `neighborCoords(5, 3)[0]` = `(5+1, 3+0)` = `(6, 3)`. 5. The flow arrow points toward `(6, 3)` — the correct NE neighbor of an odd column. The same Rust index produces correct output for both parities via `rustDirToFlatTopDir(1, col)` regardless of `col`. --- ## 5. Implementation ```typescript // hexCanvas.ts export function rustDirToFlatTopDir(rustDir: number, col: number): number; ``` The `col` parameter is accepted for documentation clarity but the mapping is parity-independent. The function throws `RangeError` for `rustDir` outside 0–5. Verified by 12-case Vitest suite in `.project/designs/app/src/utils/worldGen/hexCanvas.test.ts`. --- ## 6. Non-goals - Changing Rust's `AXIAL_DIRECTIONS` — they are locked and match GDScript. - Changing `hexCorners` orientation — flat-top is the canvas baseline. - Godot translation — Rust's pointy-top labels match GDScript's `HexUtils`, so no bridge-crossing translation is needed on the Godot side.