reikhelm/.dev/2026-05-28-procgen-map-core/state/progress.md
Parley Hatch 5f195fdb0b chore: record Task 13 + final v1 verification (all 13 tasks complete)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:34:21 -06:00

10 KiB
Raw Permalink Blame History

Progress Ledger — reikhelm procgen core

Orchestrator's external memory. One entry per task as it completes.

Environment notes

  • Rust 1.96.0 stable. ~/.cargo/bin NOT on PATH — prepend export PATH="$HOME/.cargo/bin:$PATH" in every cargo shell call.
  • Resolved dep versions: rand 0.10.1, rand_chacha 0.10.0, rand_core 0.10.1, serde 1.0.228 (derive), macroquad 0.4.15. rand 0.10 line — RNG task must target 0.10 API (prefer building helpers on RngCore::next_u64).

Execution schedule

  • Phase 0: Task 1 (scaffold) — direct dispatch, de-risk toolchain/version resolution.
  • Phase 1: Tasks 2→3→5 chain + Task 4 (RNG) — core library layer.
  • Phase 2: Task 6 (engine) — keystone Pass/Pipeline contract + validator.
  • Phase 2.5: orchestrator pre-stubs 5 pass files + wires passes/mod.rs.
  • Phase 3: Tasks 711 (passes) — parallel, worktree-isolated; then adversarial verify.
  • Phase 4: Task 12 (recipe + integration) — heavy determinism/connectivity verification.
  • Phase 5: Task 13 (viz) — build + boundary check.

Task log

Task 1 — Workspace scaffold (commit 08e99e3)

  • Two-crate workspace, resolver "2", edition 2021. core deps: rand/rand_chacha/serde(derive); viz deps: macroquad + reikhelm-core (path). cargo build+cargo test green (0 tests). lib.rs has only crate doc; main.rs plain fn main() stub. .dev/ plan committed into repo root (fine for greenfield). No rendering dep in core ✓.

Tasks 25 — core library layer (commits f707bd1, dbfd44a, 6ffe0cc, 659ef00)

Ran as Phase 1 workflow (sequential 2→3→4→5). All 41 tests green, no warnings. Reviewed all source directly.

  • T2 geometry: Point/Rect/Line + extras (OFFSETS4/8, offset, right/bottom/is_empty, sign). Fixed iteration orders for determinism. Point derives Hash. Line::cells handles diagonal as connected staircase (documented).
  • T3 grid: bounds-safe Grid, row-major, OOB read=None/write=no-op. iter_rect avoids Rect::iter borrow lifetime. Now also derives PartialEq/Eq (see fix below).
  • T4 rng: ChaCha8 wrapper. fork mix = splitmix64(basis) → FNV-1a over label bytes → splitmix64 finalizer. NO DefaultHasher. fork(&self) order-independent, doesn't mutate parent. range=half-open w/ rejection sampling; shuffle=hand-rolled FisherYates on next_u64; chance clamps + 53-bit. Adversarial RNG verify never ran (worktree error) — I reviewed it manually: contract holds.
  • T5 data model: Tile{Wall(default),Floor,Door}; RegionId(pub usize) id==index invariant; RegionKind{Room,Corridor}; Region/Edge fields pub; Edge.at=Option; ConnGraph new/add_edge/edges/edges_mut/neighbors; Map all-pub; to_ascii #/./+.

Orchestrator fix — derive PartialEq/Eq on Grid + Map (commit 8c4ef2e)

T5 deliberately skipped Eq on Map (Grid wasn't Eq) and used a serde-string compare workaround. Added the derives at the data-model layer so Tasks 6 & 12 determinism tests can use assert_eq!(map_a, map_b) directly. Purely additive, 41 tests still green.

⚠️ Environment constraint discovered

Worktree isolation is UNAVAILABLE (WorktreeIsolationError: not in a git repository and no WorktreeCreate hooks configured) — even though git rev-parse confirms a valid repo. Do NOT use isolation: 'worktree' in workflows or Agent calls. Phase 3 (parallel passes 711) must use a different anti-race strategy: orchestrator pre-stubs all 5 pass files + pre-wires passes/mod.rs, commits, then passes are implemented — likely SEQUENTIALLY in the shared tree (or with each agent told to only Write its file + not run concurrent cargo) to avoid build races. Re-decide at Phase 3.

Task 6 — generation engine (commit e0a20f7)

Direct dispatch + my full code review (skipped separate validator — keystone read line-by-line; Task 12 integration tests are the end-to-end backstop). 53 tests green, clippy-clean.

  • Pass::apply(&self, ctx, rng) exact. GenContext{tiles,regions,graph,blackboard} + add_regionRegionId(len()). Snapshot{label,tiles,regions,edges}. Pipeline::new(w,h)/then/run/run_with_snapshots.
  • RNG isolation correct: per-NAME occurrence counter (HashMap<&str,u32>), key "name#occ", root.fork(key) per pass. Isolation test inserts draw-then-discard X between A,B and proves their streams unchanged. ✓
  • run/run_with_snapshots share private run_inner(seed, collect) → cannot diverge. Snapshots clone-after-apply, RNG-untouched. ✓
  • Blackboard HashMap<String,Box>; wrong-type get→None, take non-destructive. ✓
  • passes/mod.rs created with contract doc comment, NO pub mod lines (711 append).

Phase 3 — five passes (commits for bsp/room/connect/corridor/door) + adversarial verify

All 5 passes individually PASS (101 tests green, clippy clean). Adversarial verifier found an EMERGENT corridor↔door integration bug (see smell-ledger). Per-pass notes: BSP tiles map exactly; RoomCarver skipped-leaf draws zero rng + Room.cells = room interior; MstConnect is rng-INDEPENDENT in v1 (deterministic from room set; Prim + sorted candidates, no hash order) — N-1 edges, connected under tie/collinear/dup centers; CorridorCarver center-to-center, one Corridor region per edge in edge order, draws one chance(0.5) leg-order coin per edge; DoorPlacer (original) correct in isolation but wall-threshold rule incompatible with pierced corridors.

DECISION (user) — door topology = Variant A, door_chance 0.5

Backed by 2000-seed experiment (A: 0 orphans, ~50/50 mix; B: 66.5% orphans). DoorPlacer being REVISED to pierce-aware door-at-mouth + DoorConfig{door_chance:0.5 default}. See smell-ledger for the exact model. This is a change to a committed pass (Task 11). Recipe (Task 12) must expose door config + adjust integration expectations (doors exist; edge.at = Some only for doored edges, None for open archways).

DoorPlacer revision (commit 93c32a6)

Pierce-aware model: threshold = pierced cell (corridor floor, not in any room) with room-MEMBERSHIP neighbor on one axis + outward-corridor on the opposite. DoorConfig{door_chance:0.5} (PartialEq only — f64; no serde, matches Bsp/RoomConfig). rng.chance drawn once per threshold (lock-step determinism), BTreeSet dedupe. edge.at: corridor-region i ↔ edge i; Some(door) only if a door borders an endpoint room AND is in that edge's corridor; None when both ends are archways (cosmetic pointer, not connectivity). Non-endpoint pierce doors exist but aren't attributed to edges (documented). 14 door tests, 103 total green, clippy clean. Reviewed code directly: correct. Task 12 must add doors: DoorConfig to DungeonConfig (not in original task spec — added by door decision) and flood Floor+Door for connectivity; some edges legitimately have at==None (archways).

Task 12 — dungeon recipe + integration (commit 4e54d3b)

dungeon(cfg)->Result<Pipeline,ConfigError> validates (ZeroDimension/RoomTooSmall/MaxSmallerThanMin/NegativeMargin/RoomLargerThanLeaf) then assembles BSP→Room→MST→Corridor→Door. Validation inequality min_leaf - 2*margin >= min_size (matches RoomCarver skip) → no skipped leaves → MST spans all → connected. DungeonConfig{width,height,bsp,rooms,connect,doors} default 64×40, door_chance 0.5, PartialEq-only (f64). 115 tests green incl: determinism, run==run_with_snapshots (5 snapshots, labels ordered), connectivity over 64 seeds (Floor|Door flood), bounds, validation errors, doors-exist + door_chance=0→0 doors but still connected (cosmetic proof) + monotonic mix. Eyeball render = recognizable connected dungeon with doors/openings mix. Reviewed validate() + connectivity test directly: correct.

Orchestrator integration stress (throwaway, deleted)

~9,200 generations all green: 5000-seed connectivity (0 orphans, default); 500-seed determinism + run==run_with_snapshots; 7 edge configs (8×8 single-room, 16×16, 120×12, 100×80, 0.6 loops, door_chance 0.0/1.0) × 300 seeds = no panic, connected, in-bounds; door_chance monotonic. Core is rock-solid.

CORE COMPLETE (Tasks 112). Remaining: Task 13 viz (interactive, can't headless-test — build + boundary review + user runs it).

Task 13 — reikhelm-viz macroquad renderer (commit 0372ce0)

Builds clean + clippy -D warnings clean. Reviewed main.rs: NO generation logic; imports only public API (recipes::dungeon::{dungeon,DungeonConfig}, pass::Snapshot, map::Tile, region::Edge, geometry::Rect). color_of in viz (not core). Reroll: seed counter in viz, Space increments. Stretch DONE: Left/Right scrub per-pass snapshots, draws region bounds (SKYBLUE) + MST edges (RED) overlay via RegionId-as-index so partition/connect stages are visible. Letterbox layout, HUD (seed/stage/hint). Cannot verify the window headless — user runs cargo run -p reikhelm-viz with a display.

v1 COMPLETE — all 13 tasks done

Final workspace verification: cargo build --workspace ok; cargo test --workspace = 115 (core) + 1 (viz color_of) + 0 green; cargo clippy --workspace --all-targets -- -D warnings clean. Acceptance: AC1 build/test ✓; AC2 connected+deterministic dungeon w/ rooms/corridors/doors ✓ (115 tests + ~9200 stress gens, 0 orphans); AC3 recognizable ASCII ✓; AC4 viz window = builds + correct, needs display to see (user step); AC5 boundary ✓ (core runtime deps = rand/rand_chacha/serde, serde_json dev-only; viz → core + macroquad).

Phase 3 plan (REVISED — no worktrees)

Run passes 7→8→9→10→11 SEQUENTIALLY in shared tree (forced by no worktree isolation). Each implementer creates its file + appends its own pub mod/pub use to passes/mod.rs (sequential = no race, like Tasks 2-5 did with lib.rs). No pre-stubbing needed. Sequential also lets later passes (e.g. DoorPlacer) read earlier passes' real cell-population conventions. Then a PARALLEL adversarial verify fan-out (read-only, no isolation) over the 5 passes for algorithm/determinism/bounds bugs. Heavy integration verification deferred to Task 12.

Open concerns

  • Determinism is the highest-risk surface: RNG fork stability (T4) + pipeline per-pass forking (T6). Both get adversarial verification.
  • Parallel batch (711) shares passes/mod.rs — handled by orchestrator-owned pre-wiring + worktree isolation.