feat(simulation): Sprint 21 — template instantiation, error handling, interactions #75

Closed
jpmschweitzer wants to merge 0 commits from server into main
Owner

Summary

Sprint 21 (Instantiate) server deliverables — 7 tickets across 8 commits:

  • #246 Basic environmental interaction: doors (toggle walkability), examine (authored text), terminals (event emission). Door state persists through save/load.
  • #108 + #109 Cross-template triangle generation and validation: 3-check validation (conflict viability, relationship coherence, interest divergence), cross-template triangles spanning two templates.
  • #85 Error handling & recovery: panic supervision (catch_unwind + final SimError snapshot), protocol error reporting via SimErrorBuffer, state hash for desync detection. Protocol v17.
  • #159 Tier 2 template definition format: FullTemplateDef with roles, spaces, triangles, dialogue pool refs, routines, sightline zones. logistics-hub.yaml fixture.
  • #166 Template-to-instance mapping: spawn_template_npcs (3-phase: spawn per role, wire relationships, record cross-template refs).
  • #161 Template instantiation engine: end-to-end pipeline (YAML → FullTemplateDef → spawn → triangles → ownership), instance lifecycle (unload/despawn), ActiveTemplateInstances resource.

Test plan

  • cargo test — all tests pass, zero warnings
  • Protocol v17 fixtures regenerated (msgpack snapshots, golden files)
  • Template instantiation end-to-end: logistics-hub.yaml → 4 NPCs + 2 TriangleStates
  • Lifecycle: instantiate → unload → all entities cleaned up
  • Determinism: same seed produces identical layout (D-010)
  • Error paths: malformed input → SimError reported, missing YAML → Err
  • Triangle validation: 10 tests covering all failure modes + valid pass
  • Door interaction: toggle walkability + save/load round-trip

🤖 Generated with Claude Code

## Summary Sprint 21 (Instantiate) server deliverables — 7 tickets across 8 commits: - **#246** Basic environmental interaction: doors (toggle walkability), examine (authored text), terminals (event emission). Door state persists through save/load. - **#108 + #109** Cross-template triangle generation and validation: 3-check validation (conflict viability, relationship coherence, interest divergence), cross-template triangles spanning two templates. - **#85** Error handling & recovery: panic supervision (catch_unwind + final SimError snapshot), protocol error reporting via SimErrorBuffer, state hash for desync detection. Protocol v17. - **#159** Tier 2 template definition format: FullTemplateDef with roles, spaces, triangles, dialogue pool refs, routines, sightline zones. logistics-hub.yaml fixture. - **#166** Template-to-instance mapping: spawn_template_npcs (3-phase: spawn per role, wire relationships, record cross-template refs). - **#161** Template instantiation engine: end-to-end pipeline (YAML → FullTemplateDef → spawn → triangles → ownership), instance lifecycle (unload/despawn), ActiveTemplateInstances resource. ## Test plan - [x] `cargo test` — all tests pass, zero warnings - [x] Protocol v17 fixtures regenerated (msgpack snapshots, golden files) - [x] Template instantiation end-to-end: logistics-hub.yaml → 4 NPCs + 2 TriangleStates - [x] Lifecycle: instantiate → unload → all entities cleaned up - [x] Determinism: same seed produces identical layout (D-010) - [x] Error paths: malformed input → SimError reported, missing YAML → Err - [x] Triangle validation: 10 tests covering all failure modes + valid pass - [x] Door interaction: toggle walkability + save/load round-trip 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jpmschweitzer added 8 commits 2026-02-27 18:04:17 +01:00
- Add ValidationError enum with three failure modes: ConflictViability
  (missing Want axis), RelationshipCoherence (empty constraints),
  InterestDivergence (duplicate interest axes)
- Add validate_triangle_def() pure function enforcing all three checks
  in priority order (per D-087)
- Add generate_cross_template_triangles() function that combines NPC
  pools from two templates, validates each TriangleDef before processing,
  and assigns ownership to template_a
- 10 integration tests in tests/triangle_validation.rs covering all
  validation failure modes, ordering guarantees, cross-template span,
  invalid def skipping, determinism, and intra-template isolation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add DoorState component tracking is_open and blocking_tile; add
  DoorInteractRequest per-player component consumed by new
  process_door_interaction system (toggles walkability each use)
- Add TerminalInteracted event, TerminalInteractedQueue resource,
  TerminalInteractRequest component, and process_terminal_interaction
  system (emits event on Use verb)
- Add ExamineText(String) component for authored object examine text;
  extend process_examine_interaction with object examine path:
  uses ExamineText if present, falls back to generic string if absent
- Fix: add Without<ObjectType> filter to npc_query in
  process_examine_interaction — previously any entity with TilePosition
  was mis-routed through the NPC text generator
- Add SaveStateV1.open_doors: Vec<StableId> with #[serde(default)]
  for backward-compatible serialization
- Add "Open"/"Close" → DoorInteractRequest and "Use" →
  TerminalInteractRequest dispatch in process_player_input
- 10 integration tests in tests/environmental_interaction.rs covering
  all acceptance criteria: door toggle (both directions), open-to-close,
  invalid target, readable examine (with/without ExamineText), out-of-range,
  terminal event emission, request cleanup, and save state round-trip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Follow-up to b6a9b78: register TerminalInteractedQueue resource and
door/terminal interaction systems in SimulationPlugin; add door state
save/load in save_io (open doors round-trip through SaveStateV1);
make WalkabilityMap param optional in process_door_interaction so
plugin-only tests work without a loaded map; fix information_boundaries
test missing open_doors field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Protocol v17: add state_hash (desync detection) and sim_errors
(structured error reporting) to ObserverSnapshot. Add SimError,
SimErrorKind, SimErrorBuffer types. Wrap main loop app.update() in
catch_unwind — on panic, send a final SimError snapshot before exit.
Report recoverable deserialization errors to client via SimErrorBuffer.
Compute per-tick state hash from player position + NPC count + tick.
Update all test fixtures and golden files for protocol v17.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add FullTemplateDef integration tests: round-trip YAML serialization,
space spec validation, routine schedule, sightline zones, dialogue pool
refs, cross-template link specs, and logistics-hub template fixture.
Tests cover the full social site template document structure per D-023,
D-024, D-025, D-028.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add spawn_template_npcs: three-phase template instantiation (spawn NPCs
per role slot, wire intra-template relationships, record cross-template
references in TemplateReferenceMap). Partially complete — needs
validation pass, error handling, and integration with content loading
pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove duplicate #[test] attribute, unused TemplateId import, and dead
spawn_escalation_npc helper function (no longer referenced after #250
refactor).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add content::instantiation module with:
- instantiate_template(): validates FullTemplateDef, calls
  spawn_template_npcs, generates TriangleState entities with ActiveSim,
  registers in ActiveTemplateInstances resource
- unload_template(): despawns all NPC + triangle entities, removes
  from tracking
- load_template_from_file(): YAML → FullTemplateDef deserialization
- ActiveTemplateInstances: BTreeMap-backed resource (D-010 determinism)

Integration tests: end-to-end logistics-hub.yaml instantiation (4 NPCs,
2 TriangleStates), lifecycle (instantiate → unload → clean), determinism
(same seed = same layout), error path (missing file).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Test comment to verify tool is working

Test comment to verify tool is working
Author
Owner

Tyre (Architecture) — PR #75 Review

Verdict: REQUEST_CHANGES

Summary: This is strong, well-structured work. The template instantiation pipeline is clean, the three-phase spawn model (spawn → wire relationships → record cross-template refs) maps correctly to the architecture, and the documentation throughout is excellent. Two issues need fixing before merge: a critical component gap in spawn_template_npcs that will silently break vision/awareness for all template-spawned NPCs, and a validation asymmetry between intra- and cross-template triangle generation. There are also two warning-level concerns worth addressing in this sprint.


CRITICAL

1. spawn_template_npcs does not insert NpcVisionState / NpcMemory / PlayerAwareness

File: /var/mnt/data/projects/settled-reach/server/server/src/content/spawn.rs, function spawn_template_npcs (line ~717–748)

PR #66 fixed spawn_npc() (the content-loader path) to insert these three components so NPCs are visible to the vision and awareness systems. spawn_template_npcs is a new spawn path added in this PR and does not replicate that fix. Template-spawned NPCs will be silently invisible to NpcVisionState, NpcMemory, and PlayerAwareness systems — the exact regression that #66 fixed for content-spawned NPCs.

The fix is the same block already in spawn_npc() at line ~219–223 in the current file:

entity_commands.insert((
    npc::vision::NpcVisionState::default(),
    npc::vision::NpcMemory::default(),
    npc::awareness::PlayerAwareness::default(),
));

This needs to be added after generate_npc() returns the entity in spawn_template_npcs. This is the most consequential gap in the PR.


2. generate_intra_template_triangles does not call validate_triangle_def — cross-template path does, intra-template path does not

File: /var/mnt/data/projects/settled-reach/server/server/src/content/template.rs

generate_cross_template_triangles (line ~904–912) validates each TriangleDef with validate_triangle_def before assigning roles. generate_intra_template_triangles (line ~684–741) skips this step entirely — it goes directly to role assignment without running the three quality checks (ConflictViability, RelationshipCoherence, InterestDivergence).

This means invalid intra-template triangles (e.g., zero Want axes, empty relationship_constraints) will silently produce TriangleState entities that will never escalate meaningfully. The validation machinery was written specifically to catch these cases.

Fix: add the same validation gate at the top of the for def in defs loop in generate_intra_template_triangles:

if let Err(e) = validate_triangle_def(def) {
    result.warnings.push(format!(
        "Triangle {:?}: skipped — validation failed: {}",
        def.triangle_id, e
    ));
    continue;
}

WARNING

3. state_hash uses DefaultHasher — template.rs explicitly forbids this

File: /var/mnt/data/projects/settled-reach/server/server/src/perception/observer/mod.rs (line ~405–414)

compute_observer_snapshot uses std::collections::hash_map::DefaultHasher to compute state_hash. The module-level doc in template.rs explicitly calls this out: "Never use std::hash::DefaultHasher (non-deterministic across Rust versions)" — and TemplateId::from_seed_and_slug uses FNV-1a specifically to avoid it.

The risk here is scoped: state_hash is a comparison signal (client side only, not persisted, not used to seed further state), so this won't corrupt simulation determinism. But it's an inconsistency in the codebase — if the server is ever run under a different Rust version, the hashes won't compare correctly to a client compiled under the original version. Given state_hash is specifically for desync detection, a non-deterministic hash that disagrees across compiler versions would fire false positives.

Fix: use the same FNV-1a pattern that TemplateId and TriangleId use, or add a comment explaining why DefaultHasher is acceptable in this specific context (same-binary, same-tick comparison only, never serialized).


4. TriangleCrisisEventWire sends role_assignments to client without observer KG gating

File: /var/mnt/data/projects/settled-reach/server/server/src/bridge/types.rs, TriangleCrisisEventWire (line ~706–715)
File: /var/mnt/data/projects/settled-reach/server/server/src/perception/observer/mod.rs (line ~396–401)

TriangleCrisisEventWire.role_assignments contains Vec<(String, u64)> — role slug plus stable NPC ID — for all three roles in the triangle. This is drained from the global TriangleCrisisEventQueue unconditionally into the observer snapshot, bypassing D-010 principle 2 (information boundaries are universal).

A player who has never met NPC Stable ID 42 will receive that NPC's identity in the role_assignments list the moment a triangle they're in transitions to Active phase.

For v0.1 the client comments say "client may render a narrative event or HUD indicator" and implies this is future work. The fields being unconditionally sent is the concern, not the client rendering. The fix is to filter role_assignments against the observer's KnowledgeGraph before building the wire type — or document this as an explicit known deferred item with a ticket so it doesn't get forgotten.


SUGGESTION

5. FullTemplateDef.validate() does not call validate_triangle_def

File: /var/mnt/data/projects/settled-reach/server/server/src/content/template.rs, FullTemplateDef::validate() (line ~546–587)

validate() runs structural checks (no duplicate roles, trust range validity, role references exist) but does not run validate_triangle_def on each triangle. The three quality checks from #109 (ConflictViability, RelationshipCoherence, InterestDivergence) are only run at instantiation time in generate_cross_template_triangles — and as noted above, not at all in generate_intra_template_triangles.

Calling validate_triangle_def in FullTemplateDef::validate() would catch degenerate triangles at YAML load time rather than at world-gen time, and would make the instantiation-time calls redundant rather than essential. This is a tooling-quality improvement, not a runtime correctness issue.


Architectural assessment (what's working well)

The template → instance lifecycle is clean. instantiate_template validates before any ECS mutations, the three-phase spawn maps to the D-025 ownership model correctly, and unload_template is complete (despawns both NPC and triangle entities). ActiveTemplateInstances using BTreeMap<u64, TemplateInstance> is correct for D-010.

The FNV-1a usage in TemplateId::from_seed_and_slug and TriangleId::from_seed_and_roles is exactly right. The separator byte trick in TriangleId to prevent "ab"+"c" == "a"+"bc" hash collisions is a nice detail that shows the author understood the problem.

Door state persistence in save_state.rs via open_doors: Vec<StableId> is solid. The interaction system's phase boundary comment (Phase 1: proximity only, Phase 2 KG-gated) is architecturally clean and should be preserved.

The panic supervision in main.rs using catch_unwind(AssertUnwindSafe(...)) is the right call given the subprocess model. The send_panic_error best-effort approach is correct — we're already crashing, so failing to send the error shouldn't be fatal.

Cross-template role collision handling in generate_cross_template_triangles (template_a wins via entry().or_insert) is documented and deterministic. Good.

Items 1 and 2 are blocking. Items 3 and 4 are worth fixing in this sprint since they're small changes. Item 5 is a suggestion for a follow-up ticket.

## Tyre (Architecture) — PR #75 Review **Verdict: REQUEST_CHANGES** **Summary:** This is strong, well-structured work. The template instantiation pipeline is clean, the three-phase spawn model (spawn → wire relationships → record cross-template refs) maps correctly to the architecture, and the documentation throughout is excellent. Two issues need fixing before merge: a critical component gap in `spawn_template_npcs` that will silently break vision/awareness for all template-spawned NPCs, and a validation asymmetry between intra- and cross-template triangle generation. There are also two warning-level concerns worth addressing in this sprint. --- ### CRITICAL **1. spawn_template_npcs does not insert NpcVisionState / NpcMemory / PlayerAwareness** File: `/var/mnt/data/projects/settled-reach/server/server/src/content/spawn.rs`, function `spawn_template_npcs` (line ~717–748) PR #66 fixed `spawn_npc()` (the content-loader path) to insert these three components so NPCs are visible to the vision and awareness systems. `spawn_template_npcs` is a new spawn path added in this PR and does not replicate that fix. Template-spawned NPCs will be silently invisible to `NpcVisionState`, `NpcMemory`, and `PlayerAwareness` systems — the exact regression that #66 fixed for content-spawned NPCs. The fix is the same block already in `spawn_npc()` at line ~219–223 in the current file: ```rust entity_commands.insert(( npc::vision::NpcVisionState::default(), npc::vision::NpcMemory::default(), npc::awareness::PlayerAwareness::default(), )); ``` This needs to be added after `generate_npc()` returns the entity in `spawn_template_npcs`. This is the most consequential gap in the PR. --- **2. generate_intra_template_triangles does not call validate_triangle_def — cross-template path does, intra-template path does not** File: `/var/mnt/data/projects/settled-reach/server/server/src/content/template.rs` `generate_cross_template_triangles` (line ~904–912) validates each `TriangleDef` with `validate_triangle_def` before assigning roles. `generate_intra_template_triangles` (line ~684–741) skips this step entirely — it goes directly to role assignment without running the three quality checks (ConflictViability, RelationshipCoherence, InterestDivergence). This means invalid intra-template triangles (e.g., zero Want axes, empty relationship_constraints) will silently produce `TriangleState` entities that will never escalate meaningfully. The validation machinery was written specifically to catch these cases. Fix: add the same validation gate at the top of the `for def in defs` loop in `generate_intra_template_triangles`: ```rust if let Err(e) = validate_triangle_def(def) { result.warnings.push(format!( "Triangle {:?}: skipped — validation failed: {}", def.triangle_id, e )); continue; } ``` --- ### WARNING **3. state_hash uses DefaultHasher — template.rs explicitly forbids this** File: `/var/mnt/data/projects/settled-reach/server/server/src/perception/observer/mod.rs` (line ~405–414) `compute_observer_snapshot` uses `std::collections::hash_map::DefaultHasher` to compute `state_hash`. The module-level doc in `template.rs` explicitly calls this out: "Never use `std::hash::DefaultHasher` (non-deterministic across Rust versions)" — and `TemplateId::from_seed_and_slug` uses FNV-1a specifically to avoid it. The risk here is scoped: `state_hash` is a comparison signal (client side only, not persisted, not used to seed further state), so this won't corrupt simulation determinism. But it's an inconsistency in the codebase — if the server is ever run under a different Rust version, the hashes won't compare correctly to a client compiled under the original version. Given `state_hash` is specifically for desync detection, a non-deterministic hash that disagrees across compiler versions would fire false positives. Fix: use the same FNV-1a pattern that `TemplateId` and `TriangleId` use, or add a comment explaining why `DefaultHasher` is acceptable in this specific context (same-binary, same-tick comparison only, never serialized). --- **4. TriangleCrisisEventWire sends role_assignments to client without observer KG gating** File: `/var/mnt/data/projects/settled-reach/server/server/src/bridge/types.rs`, `TriangleCrisisEventWire` (line ~706–715) File: `/var/mnt/data/projects/settled-reach/server/server/src/perception/observer/mod.rs` (line ~396–401) `TriangleCrisisEventWire.role_assignments` contains `Vec<(String, u64)>` — role slug plus stable NPC ID — for all three roles in the triangle. This is drained from the global `TriangleCrisisEventQueue` unconditionally into the observer snapshot, bypassing D-010 principle 2 (information boundaries are universal). A player who has never met NPC Stable ID 42 will receive that NPC's identity in the role_assignments list the moment a triangle they're in transitions to Active phase. For v0.1 the client comments say "client may render a narrative event or HUD indicator" and implies this is future work. The fields being unconditionally sent is the concern, not the client rendering. The fix is to filter `role_assignments` against the observer's KnowledgeGraph before building the wire type — or document this as an explicit known deferred item with a ticket so it doesn't get forgotten. --- ### SUGGESTION **5. FullTemplateDef.validate() does not call validate_triangle_def** File: `/var/mnt/data/projects/settled-reach/server/server/src/content/template.rs`, `FullTemplateDef::validate()` (line ~546–587) `validate()` runs structural checks (no duplicate roles, trust range validity, role references exist) but does not run `validate_triangle_def` on each triangle. The three quality checks from #109 (ConflictViability, RelationshipCoherence, InterestDivergence) are only run at instantiation time in `generate_cross_template_triangles` — and as noted above, not at all in `generate_intra_template_triangles`. Calling `validate_triangle_def` in `FullTemplateDef::validate()` would catch degenerate triangles at YAML load time rather than at world-gen time, and would make the instantiation-time calls redundant rather than essential. This is a tooling-quality improvement, not a runtime correctness issue. --- ### Architectural assessment (what's working well) The template → instance lifecycle is clean. `instantiate_template` validates before any ECS mutations, the three-phase spawn maps to the D-025 ownership model correctly, and `unload_template` is complete (despawns both NPC and triangle entities). `ActiveTemplateInstances` using `BTreeMap<u64, TemplateInstance>` is correct for D-010. The FNV-1a usage in `TemplateId::from_seed_and_slug` and `TriangleId::from_seed_and_roles` is exactly right. The separator byte trick in `TriangleId` to prevent "ab"+"c" == "a"+"bc" hash collisions is a nice detail that shows the author understood the problem. Door state persistence in `save_state.rs` via `open_doors: Vec<StableId>` is solid. The interaction system's phase boundary comment (Phase 1: proximity only, Phase 2 KG-gated) is architecturally clean and should be preserved. The panic supervision in `main.rs` using `catch_unwind(AssertUnwindSafe(...))` is the right call given the subprocess model. The `send_panic_error` best-effort approach is correct — we're already crashing, so failing to send the error shouldn't be fatal. Cross-template role collision handling in `generate_cross_template_triangles` (template_a wins via `entry().or_insert`) is documented and deterministic. Good. Items 1 and 2 are blocking. Items 3 and 4 are worth fixing in this sprint since they're small changes. Item 5 is a suggestion for a follow-up ticket.
Author
Owner

Review: server -> main (PR #75, type: code)

Hoshe (Code Quality): REQUEST_CHANGES

Strong work — well-structured, good test coverage for happy paths. Two issues need fixing.

# File Severity Issue
1 server/src/content/template.rs warning generate_intra_template_triangles does not call validate_triangle_def before generating TriangleState — cross-template path does. Invalid triangles will be silently produced from authored YAML.
2 server/src/content/instantiation.rs:67 warning ActiveTemplateInstances::insert silently overwrites live instances without despawning entities — orphaned ECS entities will remain in world
3 server/tests/error_handling.rs:30-165 suggestion Barrier-based test has potential deadlock if server thread panics before reaching barrier
4 server/src/simulation/examine.rs:212 suggestion Without<ObjectType> is a fragile NPC discriminator — prefer With<Npc> marker
5 server/src/content/template.rs suggestion TriangleState uses original def.triangle_id (placeholder 0), not runtime-computed ID — possible collision
6 server/src/content/spawn.rs suggestion resolve_relationships has implicit ordering dependency on template instantiation order

Tyre (Architecture): REQUEST_CHANGES

Clean lifecycle, correct D-025 ownership model, good FNV-1a usage. Two blockers plus two warnings.

# File Severity Issue
1 server/src/content/spawn.rs:717-748 critical spawn_template_npcs does not insert NpcVisionState / NpcMemory / PlayerAwareness — template-spawned NPCs will be invisible to vision/awareness systems (PR #66 regression)
2 server/src/content/template.rs warning generate_intra_template_triangles skips validate_triangle_def — asymmetry with cross-template path
3 server/src/perception/observer/mod.rs:405-414 warning state_hash uses DefaultHashertemplate.rs explicitly forbids this for determinism
4 server/src/bridge/types.rs:706-715 warning TriangleCrisisEventWire.role_assignments crosses wire without observer KG gating — bypasses D-010 principle 2
5 server/src/content/template.rs suggestion FullTemplateDef::validate() does not run validate_triangle_def — catching at load time would be better

Verdict: CHANGES REQUESTED

Blockers:

  1. Critical: spawn_template_npcs missing vision/memory/awareness components — add the same block from spawn_npc()
  2. Warning (x2): validate_triangle_def not called in intra-template path — mirror the cross-template pattern

Should fix this sprint:
3. state_hash using DefaultHasher — use FNV-1a or document the exception
4. TriangleCrisisEventWire role_assignments crossing wire ungated — filter against observer KG or file a ticket

## Review: server -> main (PR #75, type: code) ### Hoshe (Code Quality): REQUEST_CHANGES Strong work — well-structured, good test coverage for happy paths. Two issues need fixing. | # | File | Severity | Issue | |---|------|----------|-------| | 1 | `server/src/content/template.rs` | warning | `generate_intra_template_triangles` does not call `validate_triangle_def` before generating `TriangleState` — cross-template path does. Invalid triangles will be silently produced from authored YAML. | | 2 | `server/src/content/instantiation.rs:67` | warning | `ActiveTemplateInstances::insert` silently overwrites live instances without despawning entities — orphaned ECS entities will remain in world | | 3 | `server/tests/error_handling.rs:30-165` | suggestion | Barrier-based test has potential deadlock if server thread panics before reaching barrier | | 4 | `server/src/simulation/examine.rs:212` | suggestion | `Without<ObjectType>` is a fragile NPC discriminator — prefer `With<Npc>` marker | | 5 | `server/src/content/template.rs` | suggestion | `TriangleState` uses original `def.triangle_id` (placeholder 0), not runtime-computed ID — possible collision | | 6 | `server/src/content/spawn.rs` | suggestion | `resolve_relationships` has implicit ordering dependency on template instantiation order | ### Tyre (Architecture): REQUEST_CHANGES Clean lifecycle, correct D-025 ownership model, good FNV-1a usage. Two blockers plus two warnings. | # | File | Severity | Issue | |---|------|----------|-------| | 1 | `server/src/content/spawn.rs:717-748` | **critical** | `spawn_template_npcs` does not insert `NpcVisionState` / `NpcMemory` / `PlayerAwareness` — template-spawned NPCs will be invisible to vision/awareness systems (PR #66 regression) | | 2 | `server/src/content/template.rs` | warning | `generate_intra_template_triangles` skips `validate_triangle_def` — asymmetry with cross-template path | | 3 | `server/src/perception/observer/mod.rs:405-414` | warning | `state_hash` uses `DefaultHasher` — `template.rs` explicitly forbids this for determinism | | 4 | `server/src/bridge/types.rs:706-715` | warning | `TriangleCrisisEventWire.role_assignments` crosses wire without observer KG gating — bypasses D-010 principle 2 | | 5 | `server/src/content/template.rs` | suggestion | `FullTemplateDef::validate()` does not run `validate_triangle_def` — catching at load time would be better | ### Verdict: CHANGES REQUESTED **Blockers:** 1. **Critical:** `spawn_template_npcs` missing vision/memory/awareness components — add the same block from `spawn_npc()` 2. **Warning (x2):** `validate_triangle_def` not called in intra-template path — mirror the cross-template pattern **Should fix this sprint:** 3. `state_hash` using `DefaultHasher` — use FNV-1a or document the exception 4. `TriangleCrisisEventWire` role_assignments crossing wire ungated — filter against observer KG or file a ticket
jpmschweitzer added 1 commit 2026-02-27 19:02:48 +01:00
1. CRITICAL: spawn_template_npcs now inserts NpcVisionState, NpcMemory,
   PlayerAwareness on template-spawned NPCs — matches spawn_npc() pattern
   from PR #66. Without these, template NPCs were invisible to vision and
   awareness systems.

2. WARNING: generate_intra_template_triangles now calls validate_triangle_def
   before generating TriangleState — mirrors cross-template path. Updates
   test TriangleDefs and logistics-hub.yaml to pass all three quality checks
   (conflict viability, relationship coherence, interest divergence).

3. WARNING: state_hash in compute_observer_snapshot now uses FNV-1a instead
   of DefaultHasher — consistent with D-010 principle 4 and the pattern in
   TemplateId/TriangleId. Updates golden file for new hash value.

4. WARNING: TriangleCrisisEventWire.role_assignments now filtered against
   observer KnowledgeGraph — unknown NPCs redacted from wire event per
   D-010 principle 2 (information boundaries).

5. WARNING: ActiveTemplateInstances::insert now despawns previous instance
   entities before overwriting — prevents orphaned ECS entities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Review Response — All Issues Addressed

Pushed dd00a9c with fixes for all 5 review items:

# Severity Issue Fix
1 critical spawn_template_npcs missing vision/memory/awareness Added NpcVisionState + NpcMemory + PlayerAwareness insert after generate_npc()
2 warning generate_intra_template_triangles skips validate_triangle_def Added validation gate matching cross-template path; updated all test TriangleDefs and logistics-hub.yaml to pass validation
3 warning state_hash uses DefaultHasher Replaced with FNV-1a (same pattern as TemplateId/TriangleId); updated golden file
4 warning TriangleCrisisEventWire bypasses observer KG role_assignments now filtered with observer_kg.knows_entity()
5 warning ActiveTemplateInstances::insert silently overwrites Now despawns old instance entities before inserting new one

Suggestion item #5 (validate_triangle_def in FullTemplateDef::validate) deferred — the generation-time validation gate now catches these, making it a tooling improvement for a follow-up ticket.

Full test suite: 1157 passed, 0 failed, 0 warnings.

## Review Response — All Issues Addressed Pushed `dd00a9c` with fixes for all 5 review items: | # | Severity | Issue | Fix | |---|----------|-------|-----| | 1 | **critical** | spawn_template_npcs missing vision/memory/awareness | Added NpcVisionState + NpcMemory + PlayerAwareness insert after generate_npc() | | 2 | **warning** | generate_intra_template_triangles skips validate_triangle_def | Added validation gate matching cross-template path; updated all test TriangleDefs and logistics-hub.yaml to pass validation | | 3 | **warning** | state_hash uses DefaultHasher | Replaced with FNV-1a (same pattern as TemplateId/TriangleId); updated golden file | | 4 | **warning** | TriangleCrisisEventWire bypasses observer KG | role_assignments now filtered with observer_kg.knows_entity() | | 5 | **warning** | ActiveTemplateInstances::insert silently overwrites | Now despawns old instance entities before inserting new one | **Suggestion item #5** (validate_triangle_def in FullTemplateDef::validate) deferred — the generation-time validation gate now catches these, making it a tooling improvement for a follow-up ticket. Full test suite: **1157 passed, 0 failed, 0 warnings.**
Author
Owner

Re-review: server -> main (PR #75, round 2)

Hoshe (Code Quality): APPROVE

All 5 fixes verified correct. FNV-1a constants independently verified against golden test (14452262397297540338). Despawn loop safe (get_entity guard). Test fixtures updated with valid relationship_constraints.

Two low-priority notes for follow-up:

  • triangle_escalation.rs test fixture still has relationship_constraints: vec![] — passes TriangleDef::validate() (method) but would fail validate_triangle_def() (free function)
  • triangle_validation.rs intra-template boundary test now passes due to validation skip, not the role-boundary logic it claims to test

Tyre (Architecture): APPROVE

All 4 fixes verified:

  1. Vision/memory/awareness insertion in spawn_template_npcs — correct, matches spawn_npc()
  2. validate_triangle_def gate symmetric with cross-template path
  3. FNV-1a consistent with TemplateId/TriangleId canonical pattern
  4. KG gating on role_assignments correctly enforces D-010 principle 2

Despawn-before-insert maintains D-025 single-ownership. No new issues.

Verdict: APPROVED

All blockers resolved. Clean to merge.

## Re-review: server -> main (PR #75, round 2) ### Hoshe (Code Quality): APPROVE All 5 fixes verified correct. FNV-1a constants independently verified against golden test (`14452262397297540338`). Despawn loop safe (`get_entity` guard). Test fixtures updated with valid `relationship_constraints`. Two low-priority notes for follow-up: - `triangle_escalation.rs` test fixture still has `relationship_constraints: vec![]` — passes `TriangleDef::validate()` (method) but would fail `validate_triangle_def()` (free function) - `triangle_validation.rs` intra-template boundary test now passes due to validation skip, not the role-boundary logic it claims to test ### Tyre (Architecture): APPROVE All 4 fixes verified: 1. Vision/memory/awareness insertion in `spawn_template_npcs` — correct, matches `spawn_npc()` 2. `validate_triangle_def` gate symmetric with cross-template path 3. FNV-1a consistent with `TemplateId`/`TriangleId` canonical pattern 4. KG gating on `role_assignments` correctly enforces D-010 principle 2 Despawn-before-insert maintains D-025 single-ownership. No new issues. ### Verdict: APPROVED All blockers resolved. Clean to merge.
jpmschweitzer closed this pull request 2026-02-27 19:24:49 +01:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jpmschweitzer/settled-reach#75