BoundaryWall visibility sector — 1-tile wall margin beyond LOS cone
Walls at fog edge now render correctly
Test results
1133 lib tests + 51 serialization + 7 integration + 1 golden = all green
45 new tests added across all features
Cross-team notes
Client (#581): Must bump PROTOCOL_VERSION to 18 and handle debug_response field
Visual (#582, #583): Tile format published in types.rs — Araminta can start authoring
Client (#585): BoundaryWall sector ready for client-side rendering
## Summary
Sprint 23 server deliverables — 9 tickets across 4 parallel tracks:
### Spatial track (#576, #577, #578)
- TileKind enum (Floor/Wall/Void/Restricted) on WalkabilityMap
- Location YAML tile format with content loader — stamps tile data on production startup
- Chunk streaming system — cadence-gated load/unload around player position
### Storyteller track (#570, #571, #572, #579)
- EngagementRecord component — per-NPC observation/conversation/monologue metrics
- MovementHistoryBuffer resource — 3000-tick ring buffer with proximity query
- Lifecycle rules — single activation, no concurrency, terminal resolution
- activation_pass() system — full 6-step spec from #162
### Debug track (#580)
- 10 DebugCommandKind variants with handler system
- DebugResponsePayload on ObserverSnapshot
- PROTOCOL_VERSION 17 → 18
### LOS fix (#584)
- BoundaryWall visibility sector — 1-tile wall margin beyond LOS cone
- Walls at fog edge now render correctly
## Test results
- 1133 lib tests + 51 serialization + 7 integration + 1 golden = all green
- 45 new tests added across all features
## Cross-team notes
- **Client (#581):** Must bump PROTOCOL_VERSION to 18 and handle `debug_response` field
- **Visual (#582, #583):** Tile format published in types.rs — Araminta can start authoring
- **Client (#585):** BoundaryWall sector ready for client-side rendering
Spatial chain for Sprint 23 (#576, #577, #578):
- TileKind enum (Floor/Wall/Void/Restricted) on WalkabilityMap with
set_tile_kind/tile_kind API, backward-compatible with existing
is_walkable/set_walkable
- Location YAML tile format: tiles as string arrays (F/W/V/R chars),
load_location_tiles() stamps tile data onto WalkabilityMap from
ContentStore on production startup
- Chunk streaming system: ChunkLoadRadius + ChunkStreamingCadence
resources, loads/unloads chunks around player position on cadence.
v0.1 radius covers full district (no streaming stutter)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Storyteller chain for Sprint 23 (#570, #571, #572, #579):
- EngagementRecord component: per-NPC observation_time_ticks,
conversation_count, monologue_trigger_count — incremented by
perception, dialogue, and monologue systems
- MovementHistoryBuffer resource: ring buffer of player positions
over last 3000 ticks with npcs_copresent_in_window() query
- Lifecycle rules: single activation per session, no concurrency,
no cooldown, terminal resolution constants
- activation_pass() system: gate check, proximity query, engagement
scoring, unentangled-NPC routing, module selection, emits
TriangleActivatedEvent on 10-tick cadence after contamination
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Debug and LOS tracks for Sprint 23 (#580, #584):
- DebugCommandKind enum with 10 variants (AdvanceTicks,
SkipToContamination, TeleportToPosition, ForceContaminationActivate,
InspectNpc, ListTriangles, ListPopulation, GetContaminationStatus,
TeleportToLocation, ForceTriangleActivation)
- DebugResponsePayload on ObserverSnapshot, handle_debug_commands
system gated by DebugEnabled resource
- PROTOCOL_VERSION bumped 17 → 18
- VisibilitySector::BoundaryWall variant — 1-tile wall margin beyond
LOS boundary included in visible_tiles (not exploration/memory)
- compute_boundary_walls() pass in NaturalVision after FOV+cone
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add debug_response: None to all ObserverSnapshot constructors in
integration tests
- Bump PROTOCOL_VERSION assertion 17 → 18 in serialization tests
- Regenerate golden proof_room_tick_10.json (BoundaryWall tiles)
- Regenerate client msgpack fixtures for new snapshot fields
- Fix debug.rs resource optionality (Option<ResMut> for
ContaminationActive/EventQueue)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary: Well-structured sprint delivering five features with good test coverage. However, the deep pass on core systems uncovered critical issues: inverted tile_bounds cause silent map corruption via integer overflow, debug commands can corrupt game state (time rewind, player stuck in walls), and the storyteller has edge cases around duplicate NPC candidates and precision loss in long sessions.
Pass 1
#
File:line
Severity
Issue
1
storyteller/mod.rs:561
critical
.expect() in production activation_pass — panics if triangle entity is despawned between snapshot and lookup. Guard with unwrap_or_else + log.
2
bridge/debug.rs:78-301
warning
Last response wins silently drops earlier debug responses with no log.
3
bridge/types.rs:558-560
warning
DebugEnabled defaults to true in all builds — no #[cfg(debug_assertions)] guard.
4
bridge/debug.rs:116-128
warning
SkipToContamination sets tick exactly to delay threshold — off-by-one confusion in response message.
5
content/loader.rs:380-403
warning
Row/width mismatches only warned, not errored — content typos produce invisible walkability holes.
Pass 2 (Deep — Core Systems)
#
File:line
Severity
Issue
A
storyteller/mod.rs:451
warning
observation_time_ticks as f32 loses precision beyond ~16.8M ticks; score differentiation breaks in long sessions.
B
storyteller/mod.rs:512-531
warning
copresent can contain duplicate entities — same NPC added to candidates twice, doubling its RNG weight. Deduplicate before scoring.
C
content/loader.rs:380-381
critical
Inverted tile_bounds (x_max < x_min) causes i32 as usize wrap to ~18 quintillion, silently writing tiles at wrong positions. No validation.
D
content/types.rs:45
warning
DistrictMeta.description is required (no serde(default)) — missing field silently drops entire district with all NPCs/triangles.
E
bridge/debug.rs:130-150
warning
TeleportToPosition does not validate target — player can be stuck in wall/void permanently.
F
bridge/debug.rs:116-128
warning
SkipToContamination when tick already past delay silently rewinds time, causing all cooldowns to fire immediately.
G
storyteller/mod.rs:488
warning
activation_pass fires at tick 0 if contamination pre-set (save/load) — all NPCs score 0.0, selection is random.
Tyre (Architecture): REQUEST_CHANGES
Summary: Architecturally sound sprint. The tile system, chunk streaming, and storyteller activation follow established patterns. However, three critical architectural issues emerged: ActivationState is not serialized on save (breaks one-shot activation guarantee after save/load), MovementHistoryBuffer is not reset on load (stale engagement data), and activation_pass has no explicit system ordering relative to advance_tick (latent determinism risk).
Pass 1
#
File:line
Severity
Issue
1
bridge/types.rs:78
warning
ObserverSnapshot.version doc says Current: 17 — should be 18.
2
bridge/types.rs:73-75
warning
Version history missing v18 adds: entry for debug_response.
3
storyteller/mod.rs:463-465
warning
Doc claims D-025 fallback that is not implemented — misleading for reviewers.
Pass 2 (Deep — Core Systems)
#
File:line
Severity
Issue
7
save_io.rs:149-151,268-274
critical
ActivationState not serialized — save/load after activation resets the guard, causing double-activation (breaks one-shot guarantee).
8
save_io.rs:271-274
warning
TriangleActivatedQueue and MovementHistoryBuffer not reset on load — stale events/history carry over into new session.
9
storyteller/mod.rs:375,383-385
warning
activation_pass has no explicit ordering vs advance_tick — scheduler-dependent; could skip cadence tick on Bevy version change. Fix with .before(time::advance_tick).
10
save_state.rs:347-397
warning
deserialize_npc_from_frozen does not insert EngagementRecord — loaded NPCs silently score 0.0, engagement history lost after load.
Verdict: CHANGES REQUESTED
Critical issues (must fix):
ActivationState not saved (Tyre #7) — guaranteed double-activation on save/load. Same pattern as contamination_active persistence, just missing.
## Review: `server` → `main` (type: code)
### Hoshe (Code Quality): REQUEST_CHANGES
**Summary:** Well-structured sprint delivering five features with good test coverage. However, the deep pass on core systems uncovered critical issues: inverted tile_bounds cause silent map corruption via integer overflow, debug commands can corrupt game state (time rewind, player stuck in walls), and the storyteller has edge cases around duplicate NPC candidates and precision loss in long sessions.
#### Pass 1
| # | File:line | Severity | Issue |
|---|-----------|----------|-------|
| 1 | `storyteller/mod.rs:561` | critical | `.expect()` in production `activation_pass` — panics if triangle entity is despawned between snapshot and lookup. Guard with `unwrap_or_else` + log. |
| 2 | `bridge/debug.rs:78-301` | warning | Last response wins silently drops earlier debug responses with no log. |
| 3 | `bridge/types.rs:558-560` | warning | `DebugEnabled` defaults to `true` in all builds — no `#[cfg(debug_assertions)]` guard. |
| 4 | `bridge/debug.rs:116-128` | warning | `SkipToContamination` sets tick exactly to delay threshold — off-by-one confusion in response message. |
| 5 | `content/loader.rs:380-403` | warning | Row/width mismatches only warned, not errored — content typos produce invisible walkability holes. |
#### Pass 2 (Deep — Core Systems)
| # | File:line | Severity | Issue |
|---|-----------|----------|-------|
| A | `storyteller/mod.rs:451` | warning | `observation_time_ticks as f32` loses precision beyond ~16.8M ticks; score differentiation breaks in long sessions. |
| B | `storyteller/mod.rs:512-531` | warning | `copresent` can contain duplicate entities — same NPC added to candidates twice, doubling its RNG weight. Deduplicate before scoring. |
| C | `content/loader.rs:380-381` | **critical** | Inverted `tile_bounds` (`x_max < x_min`) causes `i32 as usize` wrap to ~18 quintillion, silently writing tiles at wrong positions. No validation. |
| D | `content/types.rs:45` | warning | `DistrictMeta.description` is required (no `serde(default)`) — missing field silently drops entire district with all NPCs/triangles. |
| E | `bridge/debug.rs:130-150` | warning | `TeleportToPosition` does not validate target — player can be stuck in wall/void permanently. |
| F | `bridge/debug.rs:116-128` | warning | `SkipToContamination` when tick already past delay silently **rewinds time**, causing all cooldowns to fire immediately. |
| G | `storyteller/mod.rs:488` | warning | `activation_pass` fires at tick 0 if contamination pre-set (save/load) — all NPCs score 0.0, selection is random. |
---
### Tyre (Architecture): REQUEST_CHANGES
**Summary:** Architecturally sound sprint. The tile system, chunk streaming, and storyteller activation follow established patterns. However, three critical architectural issues emerged: `ActivationState` is not serialized on save (breaks one-shot activation guarantee after save/load), `MovementHistoryBuffer` is not reset on load (stale engagement data), and `activation_pass` has no explicit system ordering relative to `advance_tick` (latent determinism risk).
#### Pass 1
| # | File:line | Severity | Issue |
|---|-----------|----------|-------|
| 1 | `bridge/types.rs:78` | warning | `ObserverSnapshot.version` doc says Current: 17 — should be 18. |
| 2 | `bridge/types.rs:73-75` | warning | Version history missing `v18 adds:` entry for debug_response. |
| 3 | `storyteller/mod.rs:463-465` | warning | Doc claims D-025 fallback that is not implemented — misleading for reviewers. |
#### Pass 2 (Deep — Core Systems)
| # | File:line | Severity | Issue |
|---|-----------|----------|-------|
| 7 | `save_io.rs:149-151,268-274` | **critical** | `ActivationState` not serialized — save/load after activation **resets the guard**, causing double-activation (breaks one-shot guarantee). |
| 8 | `save_io.rs:271-274` | warning | `TriangleActivatedQueue` and `MovementHistoryBuffer` not reset on load — stale events/history carry over into new session. |
| 9 | `storyteller/mod.rs:375,383-385` | warning | `activation_pass` has no explicit ordering vs `advance_tick` — scheduler-dependent; could skip cadence tick on Bevy version change. Fix with `.before(time::advance_tick)`. |
| 10 | `save_state.rs:347-397` | warning | `deserialize_npc_from_frozen` does not insert `EngagementRecord` — loaded NPCs silently score 0.0, engagement history lost after load. |
---
### Verdict: CHANGES REQUESTED
**Critical issues (must fix):**
1. **ActivationState not saved** (Tyre #7) — guaranteed double-activation on save/load. Same pattern as `contamination_active` persistence, just missing.
2. **Inverted tile_bounds** (Hoshe #C) — integer overflow silently corrupts walkability map. Add `x_min <= x_max` / `y_min <= y_max` validation.
3. **Storyteller `.expect()` panic** (Hoshe #1) — production panic path in core gameplay loop.
**High-priority warnings (strongly recommend fixing):**
- Save/load: reset `TriangleActivatedQueue` + `MovementHistoryBuffer` on load; insert `EngagementRecord` on NPC deserialize
- System ordering: explicit `.before(advance_tick)` on `activation_pass`
- Debug console: validate teleport target, guard time rewind, `cfg(debug_assertions)` on `DebugEnabled`
- Content loader: error (not warn) on tile dimension mismatches, make `DistrictMeta.description` optional
- Storyteller: deduplicate `copresent` list, guard tick 0 activation
Re-review: server → main — after fix commit ac68ec6
Hoshe (Code Quality): APPROVE
All 3 criticals and 14 warnings from the first review verified fixed. 3 minor new observations (all low severity, not blockers):
.unwrap() calls in ForceContaminationActivate arm — provably safe but stylistically inconsistent
SkipToContamination success message when already active shows current tick twice
minimal_world() test helper doesn't initialize TriangleCrisisEventQueue
Tyre (Architecture): APPROVE
All 7 architectural issues verified correct. #[serde(default)] on new SaveStateV1 fields ensures backwards compat with old saves. System ordering chain confirmed: validate_movement → append_player_history → activation_pass → advance_tick. One informational note about DebugEnabled being build-mode conditional — documented, intentional, fine for v0.1.
Verdict: APPROVED
## Re-review: `server` → `main` — after fix commit ac68ec6
### Hoshe (Code Quality): APPROVE
All 3 criticals and 14 warnings from the first review verified fixed. 3 minor new observations (all low severity, not blockers):
- `.unwrap()` calls in `ForceContaminationActivate` arm — provably safe but stylistically inconsistent
- `SkipToContamination` success message when already active shows current tick twice
- `minimal_world()` test helper doesn't initialize `TriangleCrisisEventQueue`
### Tyre (Architecture): APPROVE
All 7 architectural issues verified correct. `#[serde(default)]` on new `SaveStateV1` fields ensures backwards compat with old saves. System ordering chain confirmed: `validate_movement` → `append_player_history` → `activation_pass` → `advance_tick`. One informational note about `DebugEnabled` being build-mode conditional — documented, intentional, fine for v0.1.
### Verdict: APPROVED
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Sprint 23 server deliverables — 9 tickets across 4 parallel tracks:
Spatial track (#576, #577, #578)
Storyteller track (#570, #571, #572, #579)
Debug track (#580)
LOS fix (#584)
Test results
Cross-team notes
debug_responsefieldReview:
server→main(type: code)Hoshe (Code Quality): REQUEST_CHANGES
Summary: Well-structured sprint delivering five features with good test coverage. However, the deep pass on core systems uncovered critical issues: inverted tile_bounds cause silent map corruption via integer overflow, debug commands can corrupt game state (time rewind, player stuck in walls), and the storyteller has edge cases around duplicate NPC candidates and precision loss in long sessions.
Pass 1
storyteller/mod.rs:561.expect()in productionactivation_pass— panics if triangle entity is despawned between snapshot and lookup. Guard withunwrap_or_else+ log.bridge/debug.rs:78-301bridge/types.rs:558-560DebugEnableddefaults totruein all builds — no#[cfg(debug_assertions)]guard.bridge/debug.rs:116-128SkipToContaminationsets tick exactly to delay threshold — off-by-one confusion in response message.content/loader.rs:380-403Pass 2 (Deep — Core Systems)
storyteller/mod.rs:451observation_time_ticks as f32loses precision beyond ~16.8M ticks; score differentiation breaks in long sessions.storyteller/mod.rs:512-531copresentcan contain duplicate entities — same NPC added to candidates twice, doubling its RNG weight. Deduplicate before scoring.content/loader.rs:380-381tile_bounds(x_max < x_min) causesi32 as usizewrap to ~18 quintillion, silently writing tiles at wrong positions. No validation.content/types.rs:45DistrictMeta.descriptionis required (noserde(default)) — missing field silently drops entire district with all NPCs/triangles.bridge/debug.rs:130-150TeleportToPositiondoes not validate target — player can be stuck in wall/void permanently.bridge/debug.rs:116-128SkipToContaminationwhen tick already past delay silently rewinds time, causing all cooldowns to fire immediately.storyteller/mod.rs:488activation_passfires at tick 0 if contamination pre-set (save/load) — all NPCs score 0.0, selection is random.Tyre (Architecture): REQUEST_CHANGES
Summary: Architecturally sound sprint. The tile system, chunk streaming, and storyteller activation follow established patterns. However, three critical architectural issues emerged:
ActivationStateis not serialized on save (breaks one-shot activation guarantee after save/load),MovementHistoryBufferis not reset on load (stale engagement data), andactivation_passhas no explicit system ordering relative toadvance_tick(latent determinism risk).Pass 1
bridge/types.rs:78ObserverSnapshot.versiondoc says Current: 17 — should be 18.bridge/types.rs:73-75v18 adds:entry for debug_response.storyteller/mod.rs:463-465Pass 2 (Deep — Core Systems)
save_io.rs:149-151,268-274ActivationStatenot serialized — save/load after activation resets the guard, causing double-activation (breaks one-shot guarantee).save_io.rs:271-274TriangleActivatedQueueandMovementHistoryBuffernot reset on load — stale events/history carry over into new session.storyteller/mod.rs:375,383-385activation_passhas no explicit ordering vsadvance_tick— scheduler-dependent; could skip cadence tick on Bevy version change. Fix with.before(time::advance_tick).save_state.rs:347-397deserialize_npc_from_frozendoes not insertEngagementRecord— loaded NPCs silently score 0.0, engagement history lost after load.Verdict: CHANGES REQUESTED
Critical issues (must fix):
contamination_activepersistence, just missing.x_min <= x_max/y_min <= y_maxvalidation..expect()panic (Hoshe #1) — production panic path in core gameplay loop.High-priority warnings (strongly recommend fixing):
TriangleActivatedQueue+MovementHistoryBufferon load; insertEngagementRecordon NPC deserialize.before(advance_tick)onactivation_passcfg(debug_assertions)onDebugEnabledDistrictMeta.descriptionoptionalcopresentlist, guard tick 0 activationReview Fixes Applied (
ac68ec6)All 3 critical and 14 high-priority issues from the review have been addressed:
Critical (3/3 fixed)
.expect()panic in activation_passlet-elseguard + error logserde(default); 2 new testsHigh-priority (14/14 fixed)
sort + dedupbefore scoringcfg!(debug_assertions)+ testserde(default).before(advance_tick)deserialize_npc_from_frozenTest results
1319 passed, 0 failed (1138 lib + 51 serialization + integration/golden). 5 new tests added for review fixes.
Re-review:
server→main— after fix commitac68ec6Hoshe (Code Quality): APPROVE
All 3 criticals and 14 warnings from the first review verified fixed. 3 minor new observations (all low severity, not blockers):
.unwrap()calls inForceContaminationActivatearm — provably safe but stylistically inconsistentSkipToContaminationsuccess message when already active shows current tick twiceminimal_world()test helper doesn't initializeTriangleCrisisEventQueueTyre (Architecture): APPROVE
All 7 architectural issues verified correct.
#[serde(default)]on newSaveStateV1fields ensures backwards compat with old saves. System ordering chain confirmed:validate_movement→append_player_history→activation_pass→advance_tick. One informational note aboutDebugEnabledbeing build-mode conditional — documented, intentional, fine for v0.1.Verdict: APPROVED
Pull request closed