refactor(simulation): PR #137 review — audit override + regression tests + docs

Addresses Tyre, Hoshe, and lead review comments on PR #137:

- **Audit doc amendment** (Tyre E1 / Hoshe H1 / Lead): add "Lead override
  (2026-04-21)" section at top of docs/architecture/sprint-37-878-audit.md.
  Rewrites the conclusion to "DECISION: STRIP" with the cascade-based
  rationale. Preserves the original audit body as the pre-override record.

- **Regression tests** (Lead 2a-2b / Hoshe H2 / H3): add POSITIVE
  assertions of the new uniform behavior so silent reintroduction fails.
  - `phase2_container_verb_labels_uniform_regardless_of_player_state` —
    two trials (empty KG, POI-bearing KG) assert container verb labels
    equal Phase-1 defaults.
  - `monologue_pool_selection_uniform_no_archetype_key` — two observers
    with divergent MonologueState both draw from OBSERVE_NPC_LINES.

- **Decision record amendments** (Lead 3 / Tyre S2): D-032, D-035, and
  D-057 amended with Phase 6 deferral wording. "Retired pending Phase 6,
  not deferred with scaffolding." Reintroduction gate: a confirmed
  Phase 6 character-model design.

- **types.rs doc fixes** (Tyre S1 / Hoshe H5): StartupMessage protocol-
  flow comment updated to reflect no-version handshake (D-192).
  ObserverSnapshot version-history block grows a "Sprint 37 wire-format
  shifts" section documenting D-192 + #878 schema drops.

- **observer/tests.rs:944 comment** (Hoshe H6): rewritten to cite
  cascade rationale instead of the stale D-032-SUPERSEDED premise.

- **tests/run-atlas-determinism exit** (Hoshe H7): exit 0 when EXIT_CODE=2
  (venv/DB missing = skip, not fail). Preserves skip semantics for
  tests/run-all on machines without the Python venv.

Follow-up tickets filed:
- #895 (server, low): expand check-systems-db-stamp GENERATOR_SOURCES
  to cover gemma_naming.py + naming_core.py (Tyre S3).
- #896 (planning, low): add CLAUDE.md carveout for server wiki writes
  closing coverage gates (Tyre S4 / Hoshe H8).

H4 investigation: v01_integration_playthrough.rs was not the only E2E
handshake→tick→snapshot test; coverage preserved by bridge_ipc.rs,
bridge_tcp.rs, and game_loop.rs (the latter is pre-existing-broken
per #885). No replacement test needed.

1142/1142 lib tests pass. cargo clippy -- -D warnings clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 10:09:35 +02:00
co-authored by Claude Opus 4.6
parent 8f46048a75
commit b86bb00a55
7 changed files with 356 additions and 19 deletions
+126 -1
View File
@@ -941,7 +941,11 @@ fn phase2_no_contradiction_for_active_knowledge() {
#[test]
fn phase2_non_container_keeps_default_labels() {
// Readable objects keep their default labels (post-archetype cleanup, D-032 SUPERSEDED).
// Readable objects keep their Phase 1 verb labels unchanged through Phase 2.
// Regression guard: archetype-verb differentiation is Phase 6 detail — not present
// in the current server per the development cascade (CLAUDE.md). D-057 superseded.
// If this test fails, a character-class relabelling branch was reintroduced before
// Phase 6 scope is confirmed by the team lead.
let mut world = setup_world(32, 32);
let mut registry = EntityRegistry::new(0);
@@ -2596,3 +2600,124 @@ fn tell_state_none_when_npc_has_no_derived_tell_component() {
"NPC without DerivedTellState component should have tell_state = None"
);
}
// -----------------------------------------------------------------------
// Regression: Phase 2 container verb labels are uniform (D-057 / #878)
// -----------------------------------------------------------------------
#[test]
fn phase2_container_verb_labels_uniform_regardless_of_player_state() {
// Regression guard (D-057 superseded, #878 cascade purge):
// apply_phase2_verb_filter no longer has an archetype branch that relabels
// container verbs. Labels must be the Phase-1 defaults — "Open", "Search",
// "Observe" — regardless of the observer's KnowledgeGraph contents or
// relationship state with other entities.
//
// This test FAILS if a character-class verb-label branch is reintroduced
// without a confirmed Phase 6 scope decision from the team lead.
// --- Trial A: empty KnowledgeGraph ---
{
let mut world = setup_world(32, 32);
let mut registry = EntityRegistry::new(0);
let container = world
.spawn((
TilePosition::new(16, 15, 0),
crate::simulation::interaction::Interactable,
ObjectType::Container,
))
.id();
registry.register(container);
world.spawn((
PlayerCharacter,
TilePosition::new(16, 16, 0),
Facing(FacingDirection::North),
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
));
world.insert_resource(registry);
run_full_pipeline(&mut world);
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().unwrap();
assert_eq!(snapshot.nearby_interactions.len(), 1);
let labels: Vec<&str> = snapshot.nearby_interactions[0]
.verbs
.iter()
.map(|v| v.label.as_str())
.collect();
assert_eq!(
labels,
["Open", "Search", "Observe"],
"Trial A (empty KG): container verb labels must equal Phase-1 defaults"
);
}
// --- Trial B: KG with PersonOfInterest NPC nearby ---
// Player has a non-trivial knowledge state; container labels must still be
// the Phase-1 defaults — Phase 2 NPC-specific logic must not bleed into
// ObjectType::Container interactions.
{
let mut world = setup_world(32, 32);
let mut registry = EntityRegistry::new(0);
let container = world
.spawn((
TilePosition::new(16, 15, 0),
crate::simulation::interaction::Interactable,
ObjectType::Container,
))
.id();
registry.register(container);
// NPC out of close-range so Confront is not injected; still in KG as POI.
let npc = world
.spawn((
crate::npc::Npc,
TilePosition::new(16, 13, 0),
crate::simulation::interaction::Interactable,
))
.id();
let npc_sid = registry.register(npc);
let mut kg = KnowledgeGraph::new();
kg.observe_entity(npc_sid, TilePosition::new(16, 13, 0), 5);
kg.set_relationship(&npc_sid, RelationshipState::PersonOfInterest);
world.spawn((
PlayerCharacter,
TilePosition::new(16, 16, 0),
Facing(FacingDirection::North),
kg,
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
));
world.insert_resource(registry);
run_full_pipeline(&mut world);
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().unwrap();
let container_interaction = snapshot
.nearby_interactions
.iter()
.find(|i| i.object_type == Some(ObjectType::Container))
.expect("container interaction must be present");
let labels: Vec<&str> = container_interaction
.verbs
.iter()
.map(|v| v.label.as_str())
.collect();
assert_eq!(
labels,
["Open", "Search", "Observe"],
"Trial B (POI NPC in KG): container verb labels must equal Phase-1 defaults"
);
}
}