feat(simulation): character archetype, tell escalation, and news ticker (#587, #589, #591)

- Add character_archetype to StartupMessage with serde default (Detective)
- Bump PROTOCOL_VERSION to 19
- Add escalate_tells_on_activation() and expire_routine_deviations() systems
- RoutineDeviation inserted on triangle NPCs with 300-tick TTL
- Add TickerPool resource with deterministic SimRng rotation (200 ticks)
- Emit current_ticker in ObserverSnapshot when player is in bar zone
- Load ticker YAML from district content directories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 09:13:03 +01:00
co-authored by Claude Opus 4.6
parent fc24de6128
commit 3194a6e491
16 changed files with 567 additions and 22 deletions
+12 -5
View File
@@ -163,17 +163,20 @@ fn main() {
// Override SimRng with the chosen seed (SimulationPlugin defaults to seed 0)
app.insert_resource(settled_reach_server::simulation::rng::SimRng::new(seed));
// Character archetype from client's StartupMessage (#587).
let archetype = startup.character_archetype;
// Gauntlet test world for --test-mode, proof room for normal mode.
if test_mode {
#[cfg(feature = "gauntlet")]
settled_reach_server::test_world::setup_gauntlet(&mut app);
settled_reach_server::test_world::setup_gauntlet(&mut app, archetype);
#[cfg(not(feature = "gauntlet"))]
{
eprintln!("--test-mode requires the 'gauntlet' feature");
std::process::exit(1);
}
} else {
setup_proof_room(&mut app);
setup_proof_room(&mut app, archetype);
}
tracing::info!(
@@ -301,6 +304,7 @@ fn send_panic_error(app: &App, panic_msg: &str) {
triangle_crisis_events: vec![],
state_hash: None,
debug_response: None,
current_ticker: None,
sim_errors: vec![SimError {
kind: SimErrorKind::Panic,
message: format!("Simulation panic: {}", panic_msg),
@@ -362,7 +366,7 @@ fn dump_schedule_graph() {
/// Proof room: 32x32 map, wall at (16,14), player at (16,16), 3 NPCs.
/// Extracted from the original inline setup for reuse by both test-mode and normal mode.
fn setup_proof_room(app: &mut App) {
fn setup_proof_room(app: &mut App, archetype: settled_reach_server::bridge::types::CharacterArchetype) {
use settled_reach_server::knowledge::registry::EntityRegistry;
use settled_reach_server::knowledge::KnowledgeGraph;
use settled_reach_server::npc::relationships::{RelationshipEdge, RelationshipGraph};
@@ -394,8 +398,10 @@ fn setup_proof_room(app: &mut App) {
let mut registry = EntityRegistry::new(0);
// Player at (16,16) — smuggler archetype (#418, D-053)
// Player at (16,16) — archetype from StartupMessage (#587, D-053)
let profile = MovementProfile::smuggler();
let mut monologue_state = MonologueState::default();
monologue_state.character = archetype.as_monologue_key().to_string();
let player = app
.world_mut()
.spawn((
@@ -404,11 +410,12 @@ fn setup_proof_room(app: &mut App) {
Facing::default(),
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueState::default(),
monologue_state,
MonologueBuffer::default(),
SprintAnomalyQueue::default(),
CognitiveDelay::default(),
ListeningFocus::new(TilePosition::new(16, 16, 0)),
archetype,
profile,
profile.initial_stance(),
PlayerMoveCooldown::default(),