Define SpatialIndex trait with entities_in_range, entities_at, and
update methods. NaiveSpatialIndex uses Vec backend with Manhattan
distance. sync_spatial_index system auto-updates from Changed<TilePosition>.
Registered as Bevy resource. Trait abstraction allows grid/quadtree
replacement later without touching callers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ToleranceBreachEvent emitted when NPC stress exceeds per-seed threshold.
ToleranceBreached marker prevents duplicate events per episode, cleared
on recovery. check_tolerance_threshold system runs after update_mood,
integrates with mood FSM to push toward Hostile/Anxious. Background-tier
NPCs excluded. Unblocks #250 (triangle escalation).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RoleDefinition struct + generate_npc() seeds all 10 D-024 axes via
SimRng for deterministic generation. Constraint validation: no duplicate
relationship targets, stress < threshold, one routine entry per phase,
no contradictory personality trait pairs. Spawns fully-populated NPC
entity with 2-3 personality traits for tell system (#90).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TellCategory enum (Nervous, Angry, Friendly, Guarded, RoutineDeviation)
with DerivedTellState component. derive_tell_state system runs after
update_mood and detect_routine_deviation. Tell state derived from NPC
axis values per D-024: Secret+low Tolerance→Nervous, low Contentment+
Hostile→Angry, high Contentment+Friendly→Friendly, high Secret→Guarded.
v0.1 renderer is monologue text, not visual animation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RoutineDeviationEvent emitted when NPC breaks daily routine: wrong
location for day phase or wrong activity at correct location.
CurrentlyDeviating marker prevents duplicate events per episode.
Respects pathfinding-in-progress (no false positives). Feeds
observation event generator for observe_anomaly monologue triggers.
Primary detective mechanic per D-027 criterion 4.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Follow verb on interaction dispatcher. FollowTarget component tracks
target entity, proximity ticks, and LOS-lost ticks. update_follow_state
system: observation events fire at double frequency while following,
NPC suspicion increases via stress when player within 2 tiles for 60+
ticks (configurable). Follow ends on LOS lost timeout, suspicion
threshold crossed, or player issues different action. FollowStateWire
emitted in ObserverSnapshot for client HUD.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add observe_npc, hear_sound, observe_anomaly, witness_interaction, and
post_conversation triggers to monologue system. trigger_event_monologue
system fires after sound collection, NPC conversations, and walk-away.
Context tags (location, situation, character_state) populated for D-035
content pool matching. COOLDOWN_TICKS=300 anti-spam guard respected.
witness_interaction fires after overheard NPC-to-NPC conversation per
D-078. Voice sounds and ambient sounds correctly excluded from
hear_sound trigger.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Register all new systems in NpcPlugin and SimulationPlugin with correct
ordering constraints. Protocol bumped to v13: tell_state on VisibleEntity,
follow_state and PostConversationQueue on ObserverSnapshot, Follow verb
on VerbKind. Observer snapshot populates tell state from DerivedTellState
and follow state from FollowTarget. System ordering: tolerance after
mood, deviation after activity, tell after mood+deviation, follow after
visibility geometry, event monologue after conversations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Regenerate all msgpack test fixtures with tell_state and follow_state
fields. Update golden proof_room snapshot. Adjust serialization and
bridge tests for new ObserverSnapshot fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Large, well-structured sprint — 6 new systems, comprehensive unit tests across all. Code quality is high. Three warnings found.
#
File
Severity
Issue
1
bridge/types.rs:41
warning
Stale doc comment: Protocol version for forward compatibility. Current: 12. — should say 13 (constant on line 18 is correct)
2
npc/generate.rs:151-152 vs npc/mod.rs:128
warning
Want intensity generated in range 3..=9, but spec comment says 1-10 and test asserts 1..=10. Test gives false confidence — either widen generator to 1..=10 or narrow spec/test to 3..=9
3
simulation/monologue.rs:355-401
warning
trigger_recognition_monologue contains verbatim copy of the two-pass content-pool selection loop that already exists in select_pool_line (line 441). ~50 lines of duplication — maintenance hazard, call the helper instead
4
npc/generate.rs:464-485
suggestion
generate_npc spawns all 10 axis components but not TilePosition — undocumented caller invariant, add doc comment
5
npc/tell_state.rs:38-51
suggestion
TellCategory derives Default with #[default] Nervous — a neutral NPC shouldn't default to highest-salience tell. Consider removing Default derive or moving it to None
Tyre (Architecture): REQUEST_CHANGES
Architecture is sound — trait abstractions, ordering chains, and integer/BTreeMap discipline are correct. Two warnings, one false alarm corrected.
Takes ResMut<NaiveSpatialIndex> directly, not trait object. bevy_ecs limitation (can't store dyn Trait as Resource), but means Grid/BVH swap requires changing call site — not zero-touch as trait abstraction implies. Document migration cost
3
npc/generate.rs
suggestion
Cannot verify from headers that all randomness routes through SimRng. Recommend a test that verifies same-seed generation produces identical NPC output
4
simulation/spatial.rs
suggestion
O(N) scan fine at current scale (50 NPCs). Track against D-026 tick budget — Grid swap should precede sustained 60+ Active NPCs
Tyre false alarms (verified by lead):
DialogueResponseEvent.speaker_color_index and speaker_name — both have #[serde(default)]. Wire compat is correct.
All four are quick fixes. The architecture, system ordering, wire compat, and determinism guarantees are solid.
## Review: server -> main (type: code) — PR #55
### Hoshe (QA): REQUEST_CHANGES
Large, well-structured sprint — 6 new systems, comprehensive unit tests across all. Code quality is high. Three warnings found.
| # | File | Severity | Issue |
|---|------|----------|-------|
| 1 | `bridge/types.rs:41` | warning | Stale doc comment: `Protocol version for forward compatibility. Current: 12.` — should say 13 (constant on line 18 is correct) |
| 2 | `npc/generate.rs:151-152` vs `npc/mod.rs:128` | warning | Want intensity generated in range 3..=9, but spec comment says 1-10 and test asserts 1..=10. Test gives false confidence — either widen generator to 1..=10 or narrow spec/test to 3..=9 |
| 3 | `simulation/monologue.rs:355-401` | warning | `trigger_recognition_monologue` contains verbatim copy of the two-pass content-pool selection loop that already exists in `select_pool_line` (line 441). ~50 lines of duplication — maintenance hazard, call the helper instead |
| 4 | `npc/generate.rs:464-485` | suggestion | `generate_npc` spawns all 10 axis components but not `TilePosition` — undocumented caller invariant, add doc comment |
| 5 | `npc/tell_state.rs:38-51` | suggestion | `TellCategory` derives Default with `#[default] Nervous` — a neutral NPC shouldn't default to highest-salience tell. Consider removing Default derive or moving it to `None` |
### Tyre (Architecture): REQUEST_CHANGES
Architecture is sound — trait abstractions, ordering chains, and integer/BTreeMap discipline are correct. Two warnings, one false alarm corrected.
| # | File | Severity | Issue |
|---|------|----------|-------|
| 1 | `bridge/types.rs:41` | warning | Stale protocol version comment (same as Hoshe #1) |
| 2 | `simulation/spatial.rs` — `sync_spatial_index` | warning | Takes `ResMut<NaiveSpatialIndex>` directly, not trait object. bevy_ecs limitation (can't store `dyn Trait` as Resource), but means Grid/BVH swap requires changing call site — not zero-touch as trait abstraction implies. Document migration cost |
| 3 | `npc/generate.rs` | suggestion | Cannot verify from headers that all randomness routes through SimRng. Recommend a test that verifies same-seed generation produces identical NPC output |
| 4 | `simulation/spatial.rs` | suggestion | O(N) scan fine at current scale (50 NPCs). Track against D-026 tick budget — Grid swap should precede sustained 60+ Active NPCs |
**Tyre false alarms (verified by lead):**
- `DialogueResponseEvent.speaker_color_index` and `speaker_name` — both have `#[serde(default)]`. Wire compat is correct.
- `update_trust` ordering — explicitly uses `.after(crate::simulation::dialogue::process_confrontation_response)`. Cross-plugin constraint is properly specified.
### Verdict: CHANGES REQUESTED
**Actionable items for server team:**
1. Fix stale protocol version comment (12 → 13) — trivial
2. Resolve want intensity range mismatch (generator 3..=9 vs spec 1-10)
3. Extract duplicated pool selection in `trigger_recognition_monologue` to use `select_pool_line`
4. Document NaiveSpatialIndex swap-path migration cost (Tyre W2)
All four are quick fixes. The architecture, system ordering, wire compat, and determinism guarantees are solid.
- Fix protocol version comment (12 → 13) in ObserverSnapshot doc
- Widen Want intensity range from 3..=9 to 1..=10 to match spec and test
- Replace duplicated pool selection in trigger_recognition_monologue with
call to select_pool_line helper (~50 lines removed)
- Document NaiveSpatialIndex migration cost for grid/quadtree swap
- Remove misleading Default derive from TellCategory (Nervous is not a
sensible default for neutral NPCs)
- Add caller invariant doc on generate_npc (no TilePosition spawned)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Round 2: server -> main (type: code) — PR #55
Hoshe (QA): APPROVE
All 4 required fixes and both suggestions from round 1 verified. Full QA pass clean — 6 new systems with comprehensive unit tests across all.
#
File
Severity
Issue
1
simulation/follow.rs:145-147
suggestion
Two separate NPC queries (target_query + tolerance_query) are distinct components so no ECS conflict, but split is easy to misread — add comment explaining why
2
simulation/monologue.rs:676-712
suggestion
trigger_monologue still has inline pool selection loop (not required by round 1 fix, but now select_pool_line exists) — follow-up consolidation ticket
3
npc/generate.rs:269
suggestion
format!("Routine schedule") with no interpolation — clippy useless_format lint, use .to_string()
Tyre (Architecture): APPROVE
Fix commit correctly addresses all required changes. Full test suite passes (774 tests, 0 failures). Wire compatibility confirmed — TellCategory Default removal handled correctly by Option wrapper + #[serde(default)].
No new issues.
Verdict: APPROVED
## Review Round 2: server -> main (type: code) — PR #55
### Hoshe (QA): APPROVE
All 4 required fixes and both suggestions from round 1 verified. Full QA pass clean — 6 new systems with comprehensive unit tests across all.
| # | File | Severity | Issue |
|---|------|----------|-------|
| 1 | `simulation/follow.rs:145-147` | suggestion | Two separate NPC queries (`target_query` + `tolerance_query`) are distinct components so no ECS conflict, but split is easy to misread — add comment explaining why |
| 2 | `simulation/monologue.rs:676-712` | suggestion | `trigger_monologue` still has inline pool selection loop (not required by round 1 fix, but now `select_pool_line` exists) — follow-up consolidation ticket |
| 3 | `npc/generate.rs:269` | suggestion | `format!("Routine schedule")` with no interpolation — clippy `useless_format` lint, use `.to_string()` |
### Tyre (Architecture): APPROVE
Fix commit correctly addresses all required changes. Full test suite passes (774 tests, 0 failures). Wire compatibility confirmed — `TellCategory` Default removal handled correctly by `Option` wrapper + `#[serde(default)]`.
No new issues.
### 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 15 server deliverables — 7 tickets, 774 tests passing (+92 new):
Protocol bumped to v13:
tell_stateonVisibleEntity,follow_stateonObserverSnapshot,Followverb onVerbKind.Test plan
Review: server -> main (type: code) — PR #55
Hoshe (QA): REQUEST_CHANGES
Large, well-structured sprint — 6 new systems, comprehensive unit tests across all. Code quality is high. Three warnings found.
bridge/types.rs:41Protocol version for forward compatibility. Current: 12.— should say 13 (constant on line 18 is correct)npc/generate.rs:151-152vsnpc/mod.rs:128simulation/monologue.rs:355-401trigger_recognition_monologuecontains verbatim copy of the two-pass content-pool selection loop that already exists inselect_pool_line(line 441). ~50 lines of duplication — maintenance hazard, call the helper insteadnpc/generate.rs:464-485generate_npcspawns all 10 axis components but notTilePosition— undocumented caller invariant, add doc commentnpc/tell_state.rs:38-51TellCategoryderives Default with#[default] Nervous— a neutral NPC shouldn't default to highest-salience tell. Consider removing Default derive or moving it toNoneTyre (Architecture): REQUEST_CHANGES
Architecture is sound — trait abstractions, ordering chains, and integer/BTreeMap discipline are correct. Two warnings, one false alarm corrected.
bridge/types.rs:41simulation/spatial.rs—sync_spatial_indexResMut<NaiveSpatialIndex>directly, not trait object. bevy_ecs limitation (can't storedyn Traitas Resource), but means Grid/BVH swap requires changing call site — not zero-touch as trait abstraction implies. Document migration costnpc/generate.rssimulation/spatial.rsTyre false alarms (verified by lead):
DialogueResponseEvent.speaker_color_indexandspeaker_name— both have#[serde(default)]. Wire compat is correct.update_trustordering — explicitly uses.after(crate::simulation::dialogue::process_confrontation_response). Cross-plugin constraint is properly specified.Verdict: CHANGES REQUESTED
Actionable items for server team:
trigger_recognition_monologueto useselect_pool_lineAll four are quick fixes. The architecture, system ordering, wire compat, and determinism guarantees are solid.
Review Round 2: server -> main (type: code) — PR #55
Hoshe (QA): APPROVE
All 4 required fixes and both suggestions from round 1 verified. Full QA pass clean — 6 new systems with comprehensive unit tests across all.
simulation/follow.rs:145-147target_query+tolerance_query) are distinct components so no ECS conflict, but split is easy to misread — add comment explaining whysimulation/monologue.rs:676-712trigger_monologuestill has inline pool selection loop (not required by round 1 fix, but nowselect_pool_lineexists) — follow-up consolidation ticketnpc/generate.rs:269format!("Routine schedule")with no interpolation — clippyuseless_formatlint, use.to_string()Tyre (Architecture): APPROVE
Fix commit correctly addresses all required changes. Full test suite passes (774 tests, 0 failures). Wire compatibility confirmed —
TellCategoryDefault removal handled correctly byOptionwrapper +#[serde(default)].No new issues.
Verdict: APPROVED
Pull request closed