- 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>
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:
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:
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
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
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.
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:
Critical:spawn_template_npcs missing vision/memory/awareness components — add the same block from spawn_npc()
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
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>
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.**
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:
Vision/memory/awareness insertion in spawn_template_npcs — correct, matches spawn_npc()
validate_triangle_def gate symmetric with cross-template path
FNV-1a consistent with TemplateId/TriangleId canonical pattern
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.
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 21 (Instantiate) server deliverables — 7 tickets across 8 commits:
Test plan
cargo test— all tests pass, zero warnings🤖 Generated with Claude Code
Test comment to verify tool is working
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_npcsthat 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, functionspawn_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_npcsis a new spawn path added in this PR and does not replicate that fix. Template-spawned NPCs will be silently invisible toNpcVisionState,NpcMemory, andPlayerAwarenesssystems — 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:This needs to be added after
generate_npc()returns the entity inspawn_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.rsgenerate_cross_template_triangles(line ~904–912) validates eachTriangleDefwithvalidate_triangle_defbefore 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
TriangleStateentities 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 defsloop ingenerate_intra_template_triangles: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_snapshotusesstd::collections::hash_map::DefaultHasherto computestate_hash. The module-level doc intemplate.rsexplicitly calls this out: "Never usestd::hash::DefaultHasher(non-deterministic across Rust versions)" — andTemplateId::from_seed_and_sluguses FNV-1a specifically to avoid it.The risk here is scoped:
state_hashis 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. Givenstate_hashis 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
TemplateIdandTriangleIduse, or add a comment explaining whyDefaultHasheris 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_assignmentscontainsVec<(String, u64)>— role slug plus stable NPC ID — for all three roles in the triangle. This is drained from the globalTriangleCrisisEventQueueunconditionally 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_assignmentsagainst 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 runvalidate_triangle_defon each triangle. The three quality checks from #109 (ConflictViability, RelationshipCoherence, InterestDivergence) are only run at instantiation time ingenerate_cross_template_triangles— and as noted above, not at all ingenerate_intra_template_triangles.Calling
validate_triangle_definFullTemplateDef::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_templatevalidates before any ECS mutations, the three-phase spawn maps to the D-025 ownership model correctly, andunload_templateis complete (despawns both NPC and triangle entities).ActiveTemplateInstancesusingBTreeMap<u64, TemplateInstance>is correct for D-010.The FNV-1a usage in
TemplateId::from_seed_and_slugandTriangleId::from_seed_and_rolesis exactly right. The separator byte trick inTriangleIdto prevent "ab"+"c" == "a"+"bc" hash collisions is a nice detail that shows the author understood the problem.Door state persistence in
save_state.rsviaopen_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.rsusingcatch_unwind(AssertUnwindSafe(...))is the right call given the subprocess model. Thesend_panic_errorbest-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 viaentry().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.
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.
server/src/content/template.rsgenerate_intra_template_trianglesdoes not callvalidate_triangle_defbefore generatingTriangleState— cross-template path does. Invalid triangles will be silently produced from authored YAML.server/src/content/instantiation.rs:67ActiveTemplateInstances::insertsilently overwrites live instances without despawning entities — orphaned ECS entities will remain in worldserver/tests/error_handling.rs:30-165server/src/simulation/examine.rs:212Without<ObjectType>is a fragile NPC discriminator — preferWith<Npc>markerserver/src/content/template.rsTriangleStateuses originaldef.triangle_id(placeholder 0), not runtime-computed ID — possible collisionserver/src/content/spawn.rsresolve_relationshipshas implicit ordering dependency on template instantiation orderTyre (Architecture): REQUEST_CHANGES
Clean lifecycle, correct D-025 ownership model, good FNV-1a usage. Two blockers plus two warnings.
server/src/content/spawn.rs:717-748spawn_template_npcsdoes not insertNpcVisionState/NpcMemory/PlayerAwareness— template-spawned NPCs will be invisible to vision/awareness systems (PR #66 regression)server/src/content/template.rsgenerate_intra_template_trianglesskipsvalidate_triangle_def— asymmetry with cross-template pathserver/src/perception/observer/mod.rs:405-414state_hashusesDefaultHasher—template.rsexplicitly forbids this for determinismserver/src/bridge/types.rs:706-715TriangleCrisisEventWire.role_assignmentscrosses wire without observer KG gating — bypasses D-010 principle 2server/src/content/template.rsFullTemplateDef::validate()does not runvalidate_triangle_def— catching at load time would be betterVerdict: CHANGES REQUESTED
Blockers:
spawn_template_npcsmissing vision/memory/awareness components — add the same block fromspawn_npc()validate_triangle_defnot called in intra-template path — mirror the cross-template patternShould fix this sprint:
3.
state_hashusingDefaultHasher— use FNV-1a or document the exception4.
TriangleCrisisEventWirerole_assignments crossing wire ungated — filter against observer KG or file a ticketReview Response — All Issues Addressed
Pushed
dd00a9cwith fixes for all 5 review items: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.
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_entityguard). Test fixtures updated with validrelationship_constraints.Two low-priority notes for follow-up:
triangle_escalation.rstest fixture still hasrelationship_constraints: vec![]— passesTriangleDef::validate()(method) but would failvalidate_triangle_def()(free function)triangle_validation.rsintra-template boundary test now passes due to validation skip, not the role-boundary logic it claims to testTyre (Architecture): APPROVE
All 4 fixes verified:
spawn_template_npcs— correct, matchesspawn_npc()validate_triangle_defgate symmetric with cross-template pathTemplateId/TriangleIdcanonical patternrole_assignmentscorrectly enforces D-010 principle 2Despawn-before-insert maintains D-025 single-ownership. No new issues.
Verdict: APPROVED
All blockers resolved. Clean to merge.
Pull request closed