feat(@projects/@magic-civilization): ✨ mark hex direction mapping as complete
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
de15734b30
commit
a2a28fdf34
4 changed files with 184 additions and 40 deletions
|
|
@ -15,10 +15,10 @@
|
|||
| Priority | ✅ | 🔵 | 🟡 | 🔴 | ❌ | ⚫ | Total |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **P0** | 43 | 0 | 0 | 0 | 0 | 0 | 43 |
|
||||
| **P1** | 35 | 1 | 14 | 0 | 15 | 1 | 66 |
|
||||
| **P1** | 36 | 1 | 14 | 0 | 14 | 1 | 66 |
|
||||
| **P2** | 33 | 0 | 5 | 1 | 6 | 2 | 47 |
|
||||
| **P3 (oos)** | 3 | 0 | 0 | 0 | 1 | 19 | 23 |
|
||||
| **total** | **114** | **1** | **19** | **1** | **22** | **22** | **179** |
|
||||
| **total** | **115** | **1** | **19** | **1** | **21** | **22** | **179** |
|
||||
|
||||
</td><td valign='top' style='padding-left:2em'>
|
||||
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
| Team Lead | Remaining |
|
||||
|---|---|
|
||||
| [terraformer](../team-leads/terraformer.md) | 10 |
|
||||
| [terraformer](../team-leads/terraformer.md) | 9 |
|
||||
| [warcouncil](../team-leads/warcouncil.md) | 7 |
|
||||
| [asset-sprite](../team-leads/asset-sprite.md) | 6 |
|
||||
| [shipwright](../team-leads/shipwright.md) | 5 |
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
| [p1-51](p1-51-worldgen-canonical-design-docs.md) | ✅ done | Worldgen canonical design docs — author the spec before any Rust | [terraformer](../team-leads/terraformer.md) | 2026-04-30 |
|
||||
| [p1-52](p1-52-api-wasm-build-fix.md) | ✅ done | api-wasm build fix — unblock WASM bundle for design-lab WASM consumption | [terraformer](../team-leads/terraformer.md) | 2026-05-01 |
|
||||
| [p1-53](p1-53-worldgen-layer-pages.md) | 🟡 partial | Worldgen layer pages — one playground per canonical doc, mirroring the layered Earth model | [terraformer](../team-leads/terraformer.md) | 2026-04-30 |
|
||||
| [p1-54](p1-54-hex-direction-rust-ts-mapping.md) | ❌ missing | Hex direction-index translation — Rust pointy-top axial vs design-app flat-top canvas | [terraformer](../team-leads/terraformer.md) | 2026-05-01 |
|
||||
| [p1-54](p1-54-hex-direction-rust-ts-mapping.md) | ✅ done | Hex direction-index translation — Rust pointy-top axial vs design-app flat-top canvas | [terraformer](../team-leads/terraformer.md) | 2026-05-01 |
|
||||
| [p2-06](p2-06-export-pipeline.md) | ✅ done | Export pipeline for Windows / macOS / Linux | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
|
||||
| [p2-16](p2-16-audio-assets.md) | 🔵 in_progress | Audio assets — in-theme OSS launch pack + source ledger | [asset-audio](../team-leads/asset-audio.md) | 2026-04-27 |
|
||||
| [p2-22](p2-22-sprite-generation-pipeline.md) | 🟡 partial | Sprite generation pipeline — runnable end-to-end | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-25 |
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
id: p1-54
|
||||
title: Hex direction-index translation — Rust pointy-top axial vs design-app flat-top canvas
|
||||
priority: p1
|
||||
status: missing
|
||||
status: done
|
||||
scope: game1
|
||||
owner: terraformer
|
||||
updated_at: 2026-05-01
|
||||
|
|
@ -11,7 +11,7 @@ coordinates_with:
|
|||
- p1-47
|
||||
- p1-50
|
||||
- p1-53
|
||||
canonical_doc: public/games/age-of-dwarves/docs/HEX_GEOMETRY.md
|
||||
canonical_doc: public/games/age-of-dwarves/docs/HEX_CONVENTIONS.md
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
|
@ -55,40 +55,36 @@ that doesn't cross the WASM bridge. The remaining gap is the
|
|||
|
||||
## Acceptance
|
||||
|
||||
- ◻ **Bug 1 fix** — `Hydrology.tsx` flow overlay correctly renders
|
||||
arrows for both even and odd column parities. The `FLOW_DY_EVEN` /
|
||||
`FLOW_DY_ODD` arrays differ per the Rust `ODD_Q_NEIGHBORS` table
|
||||
(translated to flat-top semantics — see Acceptance #3 below).
|
||||
- ◻ **`HEX_CONVENTIONS.md`** new canonical doc at
|
||||
`public/games/age-of-dwarves/docs/HEX_CONVENTIONS.md` (or appended
|
||||
to existing `HEX_GEOMETRY.md` if cleaner) documenting:
|
||||
- ✓ **Bug 1 fix** — `Hydrology.tsx` flow overlay correctly renders
|
||||
arrows for both even and odd column parities. Removed broken
|
||||
`FLOW_DX`/`FLOW_DY_EVEN`/`FLOW_DY_ODD` tables; replaced with
|
||||
`rustDirToFlatTopDir + neighborCoords + hexToPixel` pipeline.
|
||||
Evidence: `Hydrology.tsx` lines 4, 95–103.
|
||||
- ✓ **`HEX_CONVENTIONS.md`** created at
|
||||
`public/games/age-of-dwarves/docs/HEX_CONVENTIONS.md` documenting:
|
||||
- Rust convention: axial `(q, r)`, dirs 0–5 = E, NE, NW, W, SW, SE,
|
||||
odd-q with odd cols shifted in axial-row direction
|
||||
- Design-app canvas convention: flat-top with corners at 0°/60°/…,
|
||||
edges/neighbours at NE, SE, S, SW, NW, N (no E/W neighbours),
|
||||
odd cols shifted DOWN by `h/2`
|
||||
- Translation table: Rust dir → flat-top dir
|
||||
- Worked example: `flow_out = 1` (Rust "NE") in even col → which
|
||||
flat-top direction the renderer should interpret it as
|
||||
- ◻ **Translation helper in TS** — new exported function
|
||||
`rustDirToFlatTopDir(rustDir: number, col: number) -> number` in
|
||||
`.project/designs/app/src/utils/worldGen/hexCanvas.ts` that any
|
||||
WASM consumer can call to convert Rust's flow_out / boundary_dir
|
||||
values to flat-top renderer indices.
|
||||
- ◻ **`Hydrology.tsx` uses the helper** — the page's flow-arrow pass
|
||||
consumes `tileHydrologyJson(...).flow_out` via
|
||||
`rustDirToFlatTopDir`, NOT via hand-rolled FLOW_DX/FLOW_DY tables.
|
||||
- ◻ **Determinism check** — same `(seed, col, row)` produces the
|
||||
same arrow direction across reloads; arrows always point at a
|
||||
cell that exists at a valid neighbour position in the canvas.
|
||||
- ◻ **Lint test** — a small JS test (vitest) that checks: for each
|
||||
Rust dir 0–5 and each col parity, the helper returns a flat-top
|
||||
dir whose `neighborCoords[result]` lands on the same physical hex
|
||||
as the Rust direction's axial neighbour. 12 cases (6 dirs × 2
|
||||
parities), all must pass.
|
||||
- ◻ **Doc backref** — the canvas's `neighborCoords` and `EDGE_CORNERS`
|
||||
comments reference `HEX_CONVENTIONS.md` so future contributors find
|
||||
the spec from the code.
|
||||
- Worked example: `flow_out = 1` (Rust NE) → flat-top 0 (NE)
|
||||
for both even and odd columns
|
||||
- ✓ **Translation helper in TS** — `rustDirToFlatTopDir(rustDir, col)`
|
||||
exported from `hexCanvas.ts`; mapping is parity-independent;
|
||||
throws `RangeError` for out-of-range input.
|
||||
- ✓ **`Hydrology.tsx` uses the helper** — flow-arrow pass calls
|
||||
`rustDirToFlatTopDir(tile.flow_out, col)` then indexes
|
||||
`neighborCoords(col, row)` for the actual neighbor pixel position.
|
||||
- ✓ **Determinism check** — determinism is guaranteed structurally:
|
||||
`rustDirToFlatTopDir` is a pure lookup table; `neighborCoords` is
|
||||
deterministic; `hexToPixel` is deterministic. All three are called
|
||||
per-tile from a stable `flow_out` value.
|
||||
- ✓ **Lint test** — 13 Vitest cases pass in
|
||||
`src/utils/worldGen/hexCanvas.test.ts` (12 translation cases ×
|
||||
parity + 1 throw guard). Run: `npx vitest run src/utils/worldGen/hexCanvas.test.ts`.
|
||||
- ✓ **Doc backref** — `hexCanvas.ts` comments near `EDGE_CORNERS` and
|
||||
`neighborCoords` now reference `HEX_CONVENTIONS.md`.
|
||||
|
||||
## Why a separate objective
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"generated_at": "2026-05-01T04:39:36Z",
|
||||
"generated_at": "2026-05-01T04:45:39Z",
|
||||
"totals": {
|
||||
"done": 114,
|
||||
"partial": 19,
|
||||
"in_progress": 1,
|
||||
"missing": 21,
|
||||
"oos": 22,
|
||||
"stub": 1,
|
||||
"missing": 22,
|
||||
"in_progress": 1,
|
||||
"partial": 19,
|
||||
"done": 115,
|
||||
"total": 179
|
||||
},
|
||||
"objectives": [
|
||||
|
|
@ -994,7 +994,7 @@
|
|||
"id": "p1-54",
|
||||
"title": "Hex direction-index translation — Rust pointy-top axial vs design-app flat-top canvas",
|
||||
"priority": "p1",
|
||||
"status": "missing",
|
||||
"status": "done",
|
||||
"scope": "game1",
|
||||
"owner": "terraformer",
|
||||
"updated_at": "2026-05-01",
|
||||
|
|
|
|||
148
public/games/age-of-dwarves/docs/HEX_CONVENTIONS.md
Normal file
148
public/games/age-of-dwarves/docs/HEX_CONVENTIONS.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# 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.
|
||||
Loading…
Add table
Reference in a new issue