#94 — Active tier simulation (complete): - Add ActiveSim marker to all 9 test world room NPC spawns - Fix test entities in routine.rs and path_follow.rs to include ActiveSim so With<ActiveSim> queries match correctly in unit tests #99 — Tier transition logic (complete): - Implement update_tier_markers system in tier.rs - Promotes/demotes tier markers by manhattan distance from PlayerCharacter: ≤40 tiles → ActiveSim, ≤120 → BackgroundSim, beyond → StateSaved - Handles cross-z-level as u32::MAX (effectively unreachable) - No-op when no PlayerCharacter entity present (headless tests safe) - 11 new unit tests covering all distance bands and boundary cases - TierPlugin now registers the system after movement::validate_movement Also picks up extended test coverage added by hoshe: - observer/tests.rs — 230 lines of perception observer tests - sound.rs — additional sound event integration tests All 548 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
2.9 KiB
Rust
76 lines
2.9 KiB
Rust
//! Sprint Gauntlet — Room 8 (28x20)
|
|
//!
|
|
//! Tests D-055 (sprint suppresses interaction buffer) and D-016 (anomaly
|
|
//! monologue fires retroactively after sprint ends).
|
|
//!
|
|
//! Layout: Open east-west corridor. Player sprints from the west end past a
|
|
//! visible NPC at the east end. A Readable zone-marker sign at the sprint
|
|
//! start position tests that the interaction buffer is empty during sprint.
|
|
//! After the sprint ends, the delayed anomaly monologue fires.
|
|
//!
|
|
//! Observer position: (4, 8) relative = (4, 10) absolute, facing East.
|
|
//!
|
|
//! Entities (StableId 56-57):
|
|
//! sprint_zone_marker (6, 8) rel = (6, 10) abs — Readable zone sign
|
|
//! npc_sprint_target (22, 8) rel = (22, 10) abs — Visible NPC during sprint
|
|
|
|
use bevy_app::prelude::*;
|
|
|
|
use crate::bridge::types::ObjectType;
|
|
use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
|
use crate::npc::{Contentment, Npc, ToleranceThreshold, Want, WantKind};
|
|
use crate::simulation::interaction::Interactable;
|
|
use crate::simulation::movement::TilePosition;
|
|
use crate::simulation::path_follow::MovementSpeed;
|
|
use crate::simulation::tier::ActiveSim;
|
|
|
|
/// Room origin (top-left corner including walls).
|
|
const ORIGIN_X: i32 = 0;
|
|
const ORIGIN_Y: i32 = 2;
|
|
|
|
/// Spawn Sprint Gauntlet entities in canonical order (StableId 56-57).
|
|
pub fn spawn_entities(app: &mut App, registry: &mut EntityRegistry) {
|
|
// Sprint zone marker sign (StableId 56)
|
|
// Placed at the sprint-start position. During sprint the interaction buffer
|
|
// must be empty — this Readable entity tests that suppression (D-055).
|
|
let sign_pos = TilePosition::new(ORIGIN_X + 6, ORIGIN_Y + 8, 0);
|
|
let entity = app
|
|
.world_mut()
|
|
.spawn((Interactable, ObjectType::Readable, sign_pos))
|
|
.id();
|
|
let sid = registry.register(entity);
|
|
app.world_mut()
|
|
.entity_mut(entity)
|
|
.insert(StableEntityId(sid));
|
|
|
|
// Sprint target NPC (StableId 57)
|
|
// Visible at the far (east) end of the gauntlet. The player sprints past
|
|
// this NPC; if it carries a Contradicted knowledge entry the anomaly
|
|
// monologue fires after ANOMALY_DELAY_TICKS (D-055, D-016).
|
|
let npc_pos = TilePosition::new(ORIGIN_X + 22, ORIGIN_Y + 8, 0);
|
|
let entity = app
|
|
.world_mut()
|
|
.spawn((
|
|
Npc,
|
|
ActiveSim,
|
|
Interactable,
|
|
npc_pos,
|
|
Want {
|
|
primary: WantKind::Safety,
|
|
intensity: 4,
|
|
description: "Sprint Gauntlet: visible NPC during sprint".to_string(),
|
|
},
|
|
Contentment { level: 0 }, // Neutral — test NPC, contentment not load-bearing here
|
|
ToleranceThreshold {
|
|
current_stress: 10,
|
|
threshold: 50,
|
|
},
|
|
MovementSpeed::default(),
|
|
))
|
|
.id();
|
|
let sid = registry.register(entity);
|
|
app.world_mut()
|
|
.entity_mut(entity)
|
|
.insert(StableEntityId(sid));
|
|
}
|