feat(simulation): add NPC name masking with role labels and color index
Server-side infrastructure for information asymmetry in dialogue. NPCs display role labels (Worker, Supervisor, Patron) instead of real names until the player explicitly learns them via KG lookup. NpcColorIndex (stable_id % 8) persists across name reveal. ConversationEvent and DialogueResponseEvent carry display names + color indices on the wire with serde(default) for compat. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -537,6 +537,56 @@ pub fn setup_gauntlet(app: &mut App) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Dialogue fixup: wire up DialogueProfile on all Npc entities missing one ---
|
||||
// Gauntlet room builders (except dialogue_room) don't include DialogueProfile.
|
||||
// Without it, the Talk verb silently no-ops. This fixup ensures every NPC
|
||||
// can respond to Talk using content from the YAML dialogue pools.
|
||||
{
|
||||
use crate::npc::Npc;
|
||||
use crate::simulation::conversation::NpcColorIndex;
|
||||
use crate::simulation::dialogue::{CurrentMood, DialogueProfile};
|
||||
|
||||
// (location, role) pairs matching content/campaigns/.../dialogue/ YAML pools.
|
||||
// Cycling through these gives NPC variety across rooms.
|
||||
const DIALOGUE_ROLES: &[(&str, &str)] = &[
|
||||
("the-terminal", "dock-worker"),
|
||||
("the-terminal", "courier"),
|
||||
("the-terminal", "maintenance-tech"),
|
||||
("the-terminal", "new-hire"),
|
||||
("the-terminal", "scheduler"),
|
||||
("the-terminal", "shift-supervisor"),
|
||||
("the-last-shift", "bartender"),
|
||||
("the-last-shift", "bar-regular"),
|
||||
("the-last-shift", "day-worker"),
|
||||
("maintenance-corridors", "transit-worker"),
|
||||
];
|
||||
|
||||
let missing: Vec<bevy_ecs::prelude::Entity> = {
|
||||
let mut q = app
|
||||
.world_mut()
|
||||
.query_filtered::<bevy_ecs::prelude::Entity, (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::Without<DialogueProfile>,
|
||||
)>();
|
||||
q.iter(app.world()).collect()
|
||||
};
|
||||
for (i, entity) in missing.iter().enumerate() {
|
||||
let (location, role) = DIALOGUE_ROLES[i % DIALOGUE_ROLES.len()];
|
||||
let color_index = registry
|
||||
.to_stable(*entity)
|
||||
.map(|sid| (sid.0 % 8) as u8)
|
||||
.unwrap_or(0u8);
|
||||
app.world_mut().entity_mut(*entity).insert((
|
||||
DialogueProfile {
|
||||
location: location.to_string(),
|
||||
role: role.to_string(),
|
||||
},
|
||||
CurrentMood::default(),
|
||||
NpcColorIndex(color_index),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
app.insert_resource(registry);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,6 @@ use crate::simulation::path_follow::MovementSpeed;
|
||||
use crate::simulation::tier::ActiveSim;
|
||||
use crate::simulation::time::DayPhase;
|
||||
|
||||
/// Room origin (top-left corner including walls).
|
||||
const ORIGIN_X: i32 = 64;
|
||||
const ORIGIN_Y: i32 = 78;
|
||||
|
||||
/// Observer position for Shift Change tests (absolute).
|
||||
pub const OBSERVER_POS: TilePosition = TilePosition { x: 72, y: 90, z: 0 };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user