Implement phase 4 (the crawl)#12
Merged
Merged
Conversation
…r params - tools/srd_compile/encounter_tables.py: six level columns of twenty d20 rows from Dungeon_Encounters.md, hand-curated name-to-id map with compile-time resolution, packed-variant pools (veteran, vampire, hellhound), hydra variant_dice forms, npc_party rows; 'Basic Adventures' typo and singular 'Expert Adventurer' labels normalized via overrides/encounter_tables.json with provenance (apply_overrides grows list-index path segments) - combat_tables.json grows the reaction table from Encounters.md (five 2d6 bands, open outer bands); models + reaction_result clamping in core/tables.py - equipment.py: _GEAR_MECHANICS params on gear templates (torch, lantern, oil, tinder box, rations, spikes, waterskin, thieves' tools) - creation golden regenerated: gear templates now serialize the additive 'params' field (schema-additive; no behavior change)
… 4 ruleset flags - core/combat.py: roll_reaction (2d6 + caller-supplied CHA modifier, referee ReactionRolledEvent, outer-band clamping via the reaction table) - core/classes.py: thief_skill_check (d% roll-under, hear_noise in-6, pick-pockets 99 cap and twice-the-target noticed rule), detection_check (X-in-6; zero chance consumes no draw), detection_chance precedence (thief row > class tag > 1-in-6 baseline; construction dwarf-only at zero) - core/ruleset.py: deprivation_penalties (off), aoe_friendly_fire (on), formation_width_limit (on) — the first documented adaptations
…messages - crawl/dungeon.py: Direction/Position geometry (pinned NW-origin, canonical north/west edge keys, absent-edge-is-wall, implicit boundary walls), frozen content specs (doors, areas, keyed encounters, features, traps with the example-trap census expressible, transitions, wandering), and the mutable DungeonState overlay (explored cells, door state, attempt memory, piles, party location) - crawl/adventure.py: Adventure/TownSpec and validate_adventure (fail-fast dangling-reference gate) - crawl/party.py: marching order IS the member list (pinned), slowest-living movement, rank chunking, reorder-as-permutation - crawl/commands.py: the full command census with per-command mode gating as data, BattleDeclaration, CommandResult, AnyCommand + parse_command - crawl/events.py: the crawl event census, ALL_EVENT_CLASSES registry, AnyEvent union, parse_any_event; messages.py templates for every new code
…attle machine, persistence - crawl/session.py: execute() with the pure validation pre-phase, mode gating, command/event logs, listeners, flags, death records (poison-cause detection for neutralize poison's window), spawn/registry, the four new crawl streams, advance_rounds/advance_turns with light-expiry translation, provisions at day boundaries, referee command handlers, views dispatch - crawl/exploration.py: the odometer in thirds (30 unexplored / 10 explored), turn absorption, rest cadence + fatigue, memorization gates (sleep counter), provisions with standard-before-iron and waterskin satisfaction, deprivation schedule under the flag, door lifecycle (force/noise/failed-force alerting, swing-shut unless wedged, pick-lock lockout), listening with the silent-undead ambiguity, whole-cell searching, treasure trap find/remove with removal failure springing, the example-trap resolution census, light sources with tinder gate and darkness gates, location-bound cell enter hooks (oil pool, web), wandering checks with noise/bright-light/resting modifiers - crawl/encounter.py: surprise matrix pins, 2d6×10 distance, stance map, uncapped parley re-rolls, evasion speed compare, pursuit with distraction by the treasure-letter proxy, 30-round exhaustion terminal, minimum-one-turn conclusion with defeat ledger and turned release - crawl/battle.py: the SRD round order on the range track, whole-command declaration rejection, machine-found disruption, concentration release, the Phase 3 consumption census (haste, invisibility, mirror image, confusion, entangled, the enchanted-monster melee ban with break-on-engage), AoE footprint with friendly fire, ScriptedPolicy on the monster_action stream, auto-invoked morale with alternates and ML 2 rout, flee-to-120' exits, victory/flight/TPK ends - crawl/views.py: PlayerView whitelist and RefereeView; persistence.py: save/load with the migration chain, replay with ReplayVersionError; RngStreams.export_states/restore_states; location-bound web kernel support
The plan's item-12 censuses as behavioral suites over the shared two-level fixture: rejection purity by stream-state comparison, the time-cost census, odometer arithmetic, door lifecycle, listening/searching attempt memory, the example-trap resolution census, light and darkness gates, rest cadence and memorization gates, provisions and the deprivation schedule, wandering modifiers, the surprise matrix, stance map, pursuit terminals, disruption positive and negative cases, the Phase 3 consumption census, AoE footprints with friendly fire on/off, morale auto-invocation, and battle terminals.
…ster - tests/generate_phase4_goldens.py: the adaptive delve driver plus master-seed search; the recorded accepted-command log is the golden script (138 commands, 417 events from seed 20260707). tests/test_phase4_goldens.py asserts the event stream and formatted transcript byte-for-byte, per-stream final RNG states, the full milestone beat census, and — at the mid-exploration, mid-battle, and end checkpoints — load(save) == state and the reload continuing identically to the straight run - tests/test_crawl_properties.py: the spec's fuzz contract (schema-valid random command sequences reject, never raise), spatial and clock invariants, save/load round-trips on fuzzed sessions, the leak property test, and the odometer closed-form drift check - tests/test_crawl_statistics.py: chi-square over the generators — reaction bands versus 2d6, the wandering check rate, d20 row uniformity, and the number-appearing dice (treasure's third stays Phase 5; the phase-2 plan note is amended accordingly) - docs/adaptations.md: the Phase 4 pinned-interpretation entries and the register's first documented-adaptations section (deprivation_penalties, aoe_friendly_fire, formation_width_limit); the Phase 3 silence entry amended now that the stationary form ships - docs/phase-4-plan.md: implementation amendments recorded; README status
Blocking: - surprise free rounds now survive every door into battle: an attacks stance converts the monsters' pending skipped beat into the party's free round, and a battle opening on the surprised party's lost encounter beat starts with the monsters' machine-run surprise round; locked by test_encounter.py::TestSurpriseFreeRounds (the mechanism had no coverage) - stationary silence anchors from the battle cast path too (the battle's location is the party's position), and the pin is actually locked: test_exploration.py::TestStationarySilence (cell attach + silenced-area cast rejection) and test_battle.py::TestSilencedCellInBattle (declaration gate); the stale Phase 3 test comment no longer contradicts the register - the pick-lock lockout test no longer skips: a deterministic seed scan takes the first failing opening roll and asserts the lockout and the level-gain retry Non-blocking: - party retreat moves at the full encounter rate (Combat.md's own sentence), the running pursuit beginning at conversion; register updated - opened unwedged doors swing shut when the party leaves the level (transitions and town travel), not only on same-level moves - the melee-ban engagement memory records the monster actually fought - confused party members get the above-2-HD re-save on the magic stream - hygiene: exploration.party.blocked cut (a blocked move is a rejection, the plan census amended), dead FLEE_EXIT_FEET removed, _record_deaths matches DamageDealtEvent exactly and only failed death saves read as poison, action_policies declared on the session, RngStreams/Alignment typing, 'pile' reserved by validate_adventure, encounter-conclusion mid-turn pin documented and registered Golden regenerated from the same seed (20260707): the crypt door now swings shut on the stairs transition and the free-round fix shifts battle draws.
- dedicated lock for the confused party member's above-2-HD re-save (test_battle.py::TestConfusedPartyResave), the re-review's residual nit - the fuzz contract caught a real crash: SpawnMonsters executed in town opened an encounter with no dungeon cell for the combat space (formation width lookup raised). SpawnMonsters now rejects outside a dungeon, PlaceParty rejects mid-encounter, and LightSource's oil-pool path rejects in town; regression tests added and the register's session entry extended
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the phase 4 plan: the framework layer. Closes the spec's Phase 4 roadmap entry.
What ships
encounter_tables.json(six level columns of twenty d20 rows fromDungeon_Encounters.md, hand-curated name→id map with compile-time resolution, packed-variant pools, hydravariant_dice,npc_partyrows; the "Basic Adventures" typo and singular "Expert Adventurer" labels normalized viaoverrides/encounter_tables.jsonwith provenance — the spec's own overrides example).combat_tables.jsongrows the reaction table;equipment.jsongrows structured exploration gear params.roll_reaction(referee-visibility, outer-band clamping),thief_skill_check/detection_check/detection_chance(with the pinned precedence and the dwarf-only zero baseline), the threeRulesetflags (deprivation_penalties,aoe_friendly_fire,formation_width_limit) — the register's first true documented adaptations — and location-bound web support in the casting interpreter.crawl/package: the dungeon grid (canonical north/west edge keys, absent-edge-is-wall, frozen content specs, the mutableDungeonStateoverlay), the adventure container withvalidate_adventure, the party (marching order is the member list), the full command census with per-command mode gating, the crawl event census withALL_EVENT_CLASSES/parse_any_event, the exploration turn loop (the thirds odometer, doors, searching, traps, light, rest, provisions, wandering), the encounter procedure (surprise matrix, stance map, uncapped parley, evasion/pursuit with the treasure-letter intelligence proxy and the 30-round exhaustion terminal), the range-track battle machine (machine-detected disruption, the Phase 3 consumption census, deterministic AoE footprints with friendly fire, auto-invoked morale,ScriptedPolicyon themonster_actionstream),GameSessionwith the pure validation pre-phase, listeners, flags, death records, and player/referee views.replay_gamewithReplayVersionError, and the standingload(save) == replay(seed, commands)test at every golden checkpoint.Notable decisions
All pinned interpretations are in docs/adaptations.md (Phase 4 section) with docstrings and test locks; deviations discovered during implementation are recorded in the plan's "Implementation amendments" section — highlights: the party moves as a formation on the range track, the default policy's ranged forms are breath weapons only (monster missile routines carry no structured range data), every effect on an encounter's monsters releases at its end, and a single pursuit can't show both a successful distraction and the 30-round exhaustion terminal, so the milestone's flee beats split across two kennels.
Review provenance
Rubber-ducked per the AGENTS.md loop. First pass: NEEDS REVISION — three blocking findings (surprise free rounds dropped on two of three battle-entry paths with zero coverage of the mechanism; the stationary-silence register lock named a nonexistent test and the battle cast path didn't anchor the cell; the pick-lock lockout test deterministically skipped) plus non-blocking catches (retreat at Combat.md's encounter rate, swing-shut on level exit, melee-ban engagement memory, confused-PC re-save, and hygiene items). All addressed in "address rubber-duck review findings"; re-review verdict: SOLID. Sign-off notes folded in, during which the fuzz suite caught a real crash (
SpawnMonstersin town had no dungeon cell for the combat space) — now a rejection with regression tests.The creation golden regenerated once (gear templates serialize the additive
paramsfield); the delve golden regenerated once during review fixes (the crypt door now swings shut on the stairs transition; the free-round fix shifts battle draws) — both explained in their commit messages.