HEXWORK - A Lantern in the Deep: 3D ASCII terminal roguelike RPG in Rust. Entirely designed and built by Muse Spark 1.3 to evaluate if I like the model.
hexwork docs phase2-wiring-plan.md
6.9 kB
Markdown
at main

Phase 2 Wiring Plan (parent integration, executes when A–D return) #

Workstreams own their files; parent owns everything else. This doc is the exact merge contract so integration is mechanical.

1. lib.rs additions (parent edits) #

pub mod air;
pub mod fire;
pub mod fluids;
pub mod gravity;
pub mod laws;
pub mod light;
pub mod materials;
pub mod rope;
pub mod rot;
pub mod scent;
pub mod signal;
pub mod sound;
pub mod structure;

2. State additions (parent edits state.rs only) #

  • pub laws: LawLog — first-time law registry; consulted before pushing any hazard CauseLink (§4.10.5 "no silent rules").
  • pub fall_odo: BTreeMap<u64, f32> — per-actor metres descended, passed &mut to gravity::step_falling_with_mass every Move tick (total-height damage; fix-C odometer). Clear an actor's entry on teleport; entries for grounded/embedded/dead actors self-reset each call.
  • pub sounds: Vec<SoundEvent> — Sound phase drains this queue.
  • pub lights: Vec<LightSource> — lantern + carried glow + flora; Light phase consumes.
  • pub corpses: BTreeMap<(i32,i32,i32), CorpseClock> — Rot phase owns.
  • pub spillover: Vec<Spill> — remainder queue when a phase exceeds its 4k update cap; drained first next tick. Exact Spill shape TBD from workstream return signatures — keep per-system spillover INSIDE each module if they already queue it; parent only wires what exists.

3. Phase wiring (parent edits run_phase only) #

Phase Call Region scope
Move gravity::step_falling(&mut actors, &grid, ...) replaces settle stub actors
Fluids fluids::step_fluids(grid, region, rng, causes, tick) active ±72 vox
Gas air::advect_markers(grid, &self.drafts(), ...) active ±72 vox
Fire fire::step_fire(grid, region, rng, causes, tick) active ±72 vox
Signal signal::step_signal(grid, &sources, region, ...) (sources: worldgen live cable; test arena stubs) active ±72 vox
Light light::propagate_light_capped(grid, &self.lights, dirty_only=true); on lamp remove/move/douse call clear_light_box(old box) FIRST, then propagate; re-queue when spillover > 0 dirty cells
Sound drain self.sounds → sound::propagate_sound(...); AI investigate stub stays (Phase 9/10) edges+nodes
Structure structure::step_structure(grid, region, ...) dirty supports
Rot rot::step_rot(...) + scent::step_scent(...) region + corpse map

Region = active 3×3×3 chunks around player actor (±72 voxels each axis). Hymns stays stubbed (Phase 8); AETHER_MIST cookoff links already emitted by fire.rs.

4. Conflict resolution rules (parent judges by these) #

  1. Design docs beat implementation: docs/03 §3.3 > subagent API choices.
  2. Determinism law is absolute: any HashMap/Instant/threading found at wiring time is rejected back to the workstream, not patched by parent.
  3. API mismatches between workstreams (e.g. scent emission paths assumed by fluids vs. scent's real signature) are fixed at the CALLER side in state.rs, never by editing a workstream's file without its consent — if the callee's shape is wrong per spec, send it back with the exact diff required.
  4. hash_state() MUST extend to voxel contents + new maps once phases mutate them (determinism contract). Parent adds: grid contents hash (bounded: hash only loaded chunks, BTreeMap order), corpses, lights.

5. Phase 2 gate tests (parent writes tests/phase2_laws.rs AFTER wiring) #

20 rule-chain integration tests. Canonical list (each asserts the causal story, not just the end state). Reviewer-ordered adjustments folded in (review-audit C/A, xhigh):

  • sledge_opens_shortcut_loud asserts the cause LINK text ("everything hears"-class line), not a State.sounds queue entry — no such queue exists yet; the Sound phase translates link→event at wiring.
  • supported_lintel_stands uses extra == 0 stone (weakened rusted/cracked collapse is design-grounded law 11 and stays: rusted iron and extra != 0 cracked stone collapse even with support).
  • Bowling tests place the victim ≤ 0.8 m HORIZONTALLY, expect a single victim with lander-takes-ceil split; damage-0 landings never bowl.
  • Mid-fall ticks assert empty events + reduced pos.z (still-airborne = cross-strata arrival hook, not grounded).
  • boulder_down_shaft / thrown_potion expectations use TOTAL-height damage (fix-workstream-c odometer), not final-tick remainder.
  • Gas-phase tests build Vec<(u64, u64, f32)> flow triples via pack_cell from edges + drafts + cell→volume map (NOT drafts() magnitudes passed raw — drafts() returns edge-indexed magnitudes; see MAJOR-4), call the 4-arg advect_markers, and re-queue flows[budget..] tails.
  • step_structure region args are tight ≤ 64-span dirty bounds, never the whole ±72 active volume (MAX_REGION_SPAN truncation is documented).

Canonical list (each asserts the causal story, not just the end state):

  1. oil_under_door_grate_chokes_actor (the mimic kill, generic actor)
  2. smoke_rises_through_shaft
  3. chlorine_pools_upward_from_floor (levitate counter demo hook)
  4. thrown_potion_arcs_and_shatters_below
  5. boulder_down_shaft_arrives_below (cross-strata arrival)
  6. coin_throw_lures_loudest_node (voice-throw)
  7. rope_snaps_when_overloaded (CHECK GRATES link present)
  8. cut_rope_drops_actor_with_fall_damage
  9. unsupported_lintel_collapses / 10. supported_lintel_stands
  10. sledge_opens_shortcut_loud (sound event emitted)
  11. water_douses_fire_to_steam
  12. oil_ignites_and_spreads
  13. acid_blood_pits_iron_grate (cable-rat story beat)
  14. verdigris_eats_copper_wire
  15. shock_chains_through_water (tragedy path link)
  16. corpse_bloats_bursts_spawns (Rustmother link)
  17. wet_iron_rusts_over_time
  18. flour_reveals_hidden_vent (SECRET_REVEALED + link)
  19. first_time_law_logged_once (laws.rs deduplication)

Plus proptest material×hazard matrix (tests/phase2_matrix.rs, needs proptest dev-dep — parent adds AFTER workstreams land to avoid breaking their builds with a lockfile change mid-flight).

6. Gate acceptance (checklist Phase 2 gate) #

  • All 12 laws demoable in test arena (hexwork --arena lands Phase 5; until then the 20 integration tests ARE the demo, run headless).
  • Every hazard emits a first-time law log line (assert via LawLog in tests).
  • cargo fmt --check, clippy -D warnings, 24+ pre-existing tests still green, 10k-tick soak stable with NEW hash (hash WILL change — voxel contents join it; record new canonical hash in soak test comment).
  • Tick ≤ 8ms p99 narrative: criterion bench extended to a populated arena (parent adds benches/arena.rs in Phase 13 perf harness; for now assert 10k-tick soak wall time < 80s headless as a smoke bound).