feat(engine): retire D-078 overheard conversation system (#848)
Per R-012: delete conversation.rs, both overheard content files, and remove all 6 wire-up points (social_plugin, bridge/types, monologue, voice/integration). Protocol version 22 → 23. Scope confirmed by #842 audit — npc/ and content/global/ untouched. Surviving NPC components (NpcName, NpcColorIndex, NpcConversation) migrated to simulation/npc_components.rs for use by D-080 knowledge propagation. Also applies pre-existing cargo fmt debt (names.rs and 4 others). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -283,9 +283,9 @@ How the player observes and interacts with the world: camera, fog, line-of-sight
|
|||||||
### D-061: Dialogue box — unified conversation log, bottom screen, max 20% height, no portraits
|
### D-061: Dialogue box — unified conversation log, bottom screen, max 20% height, no portraits
|
||||||
- **Date:** 2026-02-13
|
- **Date:** 2026-02-13
|
||||||
- **Decision:** Dialogue occupies the bottom of the screen, max 20% height, max-width 1200px ([D-076](#d-076-dialogue-box-max-width--1200px-oq-29-resolution)). NO portraits — the NPC is on screen, a portrait is redundant. Monologue floats ABOVE the dialogue box on z-layer 7 — spatial separation allows monologue to contradict dialogue visually (character thinks one thing while NPC says another). Walk-away via WASD, dialogue fades over 300ms, no close button ([D-064](content.md#d-064-walk-away--three-phase-consequences)). Auto-pause in single-player when implant UI is open; overlay design for multiplayer readiness.
|
- **Decision:** Dialogue occupies the bottom of the screen, max 20% height, max-width 1200px ([D-076](#d-076-dialogue-box-max-width--1200px-oq-29-resolution)). NO portraits — the NPC is on screen, a portrait is redundant. Monologue floats ABOVE the dialogue box on z-layer 7 — spatial separation allows monologue to contradict dialogue visually (character thinks one thing while NPC says another). Walk-away via WASD, dialogue fades over 300ms, no close button ([D-064](content.md#d-064-walk-away--three-phase-consequences)). Auto-pause in single-player when implant UI is open; overlay design for multiplayer readiness.
|
||||||
- **Unified conversation log (Sprint 14, #535):** The dialogue box is a single chronological log — not separate panels for active vs passive dialogue. Player-NPC conversations and overheard NPC-NPC conversations ([D-078](#d-078-overheard-npc-conversation--passive-dialogue-panel-with-occlusion-filter)) flow into the same scrolling log. Each entry shows `Speaker → Target: text` with per-character name colours (hash-indexed from configurable palette in `data/dialogue-theme.yaml`). Player response options render below the log; max 3 visible. Locked options invisible ([D-062](content.md#d-062-invisible-locked-dialogue-options)).
|
- **Unified conversation log (Sprint 14, #535):** The dialogue box is a single chronological log for player-NPC conversations. Each entry shows `Speaker → Target: text` with per-character name colours (hash-indexed from configurable palette in `data/dialogue-theme.yaml`). Player response options render below the log; max 3 visible. Locked options invisible ([D-062](content.md#d-062-invisible-locked-dialogue-options)).
|
||||||
- **Entry lifecycle:** All entries share the same timeout (15s + 3s fade, configurable via theme YAML). Walk-away clears response options but preserves log entries — earned information is fair game. Panel auto-hides when all entries expire and no active conversation is in progress.
|
- **Entry lifecycle:** All entries share the same timeout (15s + 3s fade, configurable via theme YAML). Walk-away clears response options but preserves log entries — earned information is fair game. Panel auto-hides when all entries expire and no active conversation is in progress.
|
||||||
- **Passive overheard lines:** Rendered at reduced opacity (0.9) per [D-078](#d-078-overheard-npc-conversation--passive-dialogue-panel-with-occlusion-filter). No response options for overheard content. Walk-away does not fire for passive-only display — the panel dismisses naturally when entries expire or the player walks out of earshot.
|
- **Passive overheard lines:** D-078 (overheard NPC conversations) was scrapped per R-012. Passive dialogue display will be redesigned after Phase 5 walkable environment.
|
||||||
- **Rationale:** Game world stays live above the dialogue box — player sees NPC body language while talking. Monologue above + dialogue below = the character can think one thing while saying another. Max 3 options + invisible locks = player never knows what they're missing. No portrait because the NPC IS on screen. A single unified log avoids a separate UI element for overheard content and makes the flow of conversation feel natural — active and passive dialogue interleave chronologically.
|
- **Rationale:** Game world stays live above the dialogue box — player sees NPC body language while talking. Monologue above + dialogue below = the character can think one thing while saying another. Max 3 options + invisible locks = player never knows what they're missing. No portrait because the NPC IS on screen. A single unified log avoids a separate UI element for overheard content and makes the flow of conversation feel natural — active and passive dialogue interleave chronologically.
|
||||||
- **Cross-reference:** Invisible locks ([D-062](content.md#d-062-invisible-locked-dialogue-options)), confrontation ([D-063](content.md#d-063-confrontation--same-box-different-weight)), walk-away ([D-064](content.md#d-064-walk-away--three-phase-consequences)), z-stack ([D-049](#d-049-z-level-rendering-stack-8-layers)), max-width ([D-076](#d-076-dialogue-box-max-width--1200px-oq-29-resolution)), overheard NPC conversation ([D-078](#d-078-overheard-npc-conversation--passive-dialogue-panel-with-occlusion-filter))
|
- **Cross-reference:** Invisible locks ([D-062](content.md#d-062-invisible-locked-dialogue-options)), confrontation ([D-063](content.md#d-063-confrontation--same-box-different-weight)), walk-away ([D-064](content.md#d-064-walk-away--three-phase-consequences)), z-stack ([D-049](#d-049-z-level-rendering-stack-8-layers)), max-width ([D-076](#d-076-dialogue-box-max-width--1200px-oq-29-resolution)), overheard NPC conversation ([D-078](#d-078-overheard-npc-conversation--passive-dialogue-panel-with-occlusion-filter))
|
||||||
- **Source:** Control & Interaction Workshop (2026-02-13). Amended Sprint 14 (#535): unified log architecture.
|
- **Source:** Control & Interaction Workshop (2026-02-13). Amended Sprint 14 (#535): unified log architecture.
|
||||||
@@ -424,7 +424,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight
|
|||||||
- **Raised by:** Workshop — unanimous
|
- **Raised by:** Workshop — unanimous
|
||||||
- **Dissent:** None
|
- **Dissent:** None
|
||||||
- **Implements:** Ticket #548
|
- **Implements:** Ticket #548
|
||||||
- **Cross-reference:** [D-041](architecture.md#d-041-knowledge-graph-data-model), [D-083](#d-083-contradiction-detection-pipeline), [D-078](#d-078-overheard-npc-conversation--passive-dialogue-panel-with-occlusion-filter)
|
- **Cross-reference:** [D-041](architecture.md#d-041-knowledge-graph-data-model), [D-083](#d-083-contradiction-detection-pipeline), [D-078](#d-078-overheard-npc-conversation--passive-dialogue-panel-with-occlusion-filter) (D-078 scrapped per R-012; knowledge propagation system remains active independently)
|
||||||
|
|
||||||
### D-081: Unprompted Disclosure Design
|
### D-081: Unprompted Disclosure Design
|
||||||
- **Date:** 2026-02-24
|
- **Date:** 2026-02-24
|
||||||
|
|||||||
@@ -279,18 +279,14 @@ fn corp_eligible(corp: &Corp, template_scale: &str, template_corridor: &str) ->
|
|||||||
|
|
||||||
// Match sector to corridor
|
// Match sector to corridor
|
||||||
let sector = corp.geographic_sector.as_deref().unwrap_or("core");
|
let sector = corp.geographic_sector.as_deref().unwrap_or("core");
|
||||||
let corridor_matches_sector = match template_corridor {
|
match template_corridor {
|
||||||
"north_reach" | "north_reach/compact" => sector == "north_reach",
|
"north_reach" | "north_reach/compact" => sector == "north_reach",
|
||||||
"south_reach" => sector == "south_reach",
|
"south_reach" => sector == "south_reach",
|
||||||
"west_reach" | "west_reach/compact" => sector == "west_reach",
|
"west_reach" | "west_reach/compact" => sector == "west_reach",
|
||||||
"east_reach" | "inner_corridor/east_reach" => {
|
"east_reach" | "inner_corridor/east_reach" => sector == "east_reach" || sector == "core",
|
||||||
sector == "east_reach" || sector == "core"
|
|
||||||
}
|
|
||||||
"frontier" => sector == "deep_frontier",
|
"frontier" => sector == "deep_frontier",
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
}
|
||||||
|
|
||||||
corridor_matches_sector
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -308,13 +304,7 @@ fn scale_abbrev(scale_tier: &str) -> &'static str {
|
|||||||
|
|
||||||
fn to_slug(s: &str) -> String {
|
fn to_slug(s: &str) -> String {
|
||||||
s.chars()
|
s.chars()
|
||||||
.map(|c| {
|
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
|
||||||
if c.is_ascii_alphanumeric() {
|
|
||||||
c
|
|
||||||
} else {
|
|
||||||
'-'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
.split('-')
|
.split('-')
|
||||||
.filter(|p| !p.is_empty())
|
.filter(|p| !p.is_empty())
|
||||||
@@ -371,8 +361,16 @@ fn generate_pair(
|
|||||||
valid_commodities: &BTreeSet<String>,
|
valid_commodities: &BTreeSet<String>,
|
||||||
) -> Option<GeneratedPair> {
|
) -> Option<GeneratedPair> {
|
||||||
let corridor = parse_template_corridor(&template.naming_pattern);
|
let corridor = parse_template_corridor(&template.naming_pattern);
|
||||||
let halo_bid = halo_id(&corp.corp_id, &template.archetype_group, &template.scale_tier);
|
let halo_bid = halo_id(
|
||||||
let vol_bid = volume_id(&corp.corp_id, &template.archetype_group, &template.scale_tier);
|
&corp.corp_id,
|
||||||
|
&template.archetype_group,
|
||||||
|
&template.scale_tier,
|
||||||
|
);
|
||||||
|
let vol_bid = volume_id(
|
||||||
|
&corp.corp_id,
|
||||||
|
&template.archetype_group,
|
||||||
|
&template.scale_tier,
|
||||||
|
);
|
||||||
|
|
||||||
let halo_name = names::generate_halo_name(rng, corridor, &template.brand_category);
|
let halo_name = names::generate_halo_name(rng, corridor, &template.brand_category);
|
||||||
let vol_name = names::generate_volume_name(rng, corridor, &template.brand_category);
|
let vol_name = names::generate_volume_name(rng, corridor, &template.brand_category);
|
||||||
@@ -493,16 +491,16 @@ fn round2(v: f64) -> f64 {
|
|||||||
// Output serialization
|
// Output serialization
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
fn write_output(
|
fn write_output(path: &PathBuf, products: &[BrandProduct], inputs: &[BrandInput]) {
|
||||||
path: &PathBuf,
|
|
||||||
products: &[BrandProduct],
|
|
||||||
inputs: &[BrandInput],
|
|
||||||
) {
|
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
out.push_str("# Generated Minor Brand Products — The Settled Reach\n");
|
out.push_str("# Generated Minor Brand Products — The Settled Reach\n");
|
||||||
out.push_str("# Auto-generated by generate_brands binary. Do not hand-edit.\n");
|
out.push_str("# Auto-generated by generate_brands binary. Do not hand-edit.\n");
|
||||||
out.push_str("# Re-run: tooling/generate-brands\n");
|
out.push_str("# Re-run: tooling/generate-brands\n");
|
||||||
out.push_str(&format!("# Total: {} brand_products, {} brand_inputs\n\n", products.len(), inputs.len()));
|
out.push_str(&format!(
|
||||||
|
"# Total: {} brand_products, {} brand_inputs\n\n",
|
||||||
|
products.len(),
|
||||||
|
inputs.len()
|
||||||
|
));
|
||||||
|
|
||||||
for p in products {
|
for p in products {
|
||||||
out.push_str("[[brand_products]]\n");
|
out.push_str("[[brand_products]]\n");
|
||||||
@@ -524,7 +522,10 @@ fn write_output(
|
|||||||
out.push_str(&format!("origin_system = {:?}\n", sys));
|
out.push_str(&format!("origin_system = {:?}\n", sys));
|
||||||
}
|
}
|
||||||
out.push_str(&format!("terroir_locked = {}\n", p.terroir_locked));
|
out.push_str(&format!("terroir_locked = {}\n", p.terroir_locked));
|
||||||
out.push_str(&format!("currency_denomination = {:?}\n", p.currency_denomination));
|
out.push_str(&format!(
|
||||||
|
"currency_denomination = {:?}\n",
|
||||||
|
p.currency_denomination
|
||||||
|
));
|
||||||
out.push_str(&format!("shadow_viable = {}\n", p.shadow_viable));
|
out.push_str(&format!("shadow_viable = {}\n", p.shadow_viable));
|
||||||
out.push_str(&format!("brand_tier = {:?}\n", p.brand_tier));
|
out.push_str(&format!("brand_tier = {:?}\n", p.brand_tier));
|
||||||
if let Some(ref hid) = p.halo_brand_id {
|
if let Some(ref hid) = p.halo_brand_id {
|
||||||
@@ -578,7 +579,10 @@ fn print_coverage(products: &[BrandProduct]) {
|
|||||||
let min_per_corp = by_corp.values().min().copied().unwrap_or(0);
|
let min_per_corp = by_corp.values().min().copied().unwrap_or(0);
|
||||||
let max_per_corp = by_corp.values().max().copied().unwrap_or(0);
|
let max_per_corp = by_corp.values().max().copied().unwrap_or(0);
|
||||||
println!(" Corps covered: {}", by_corp.len());
|
println!(" Corps covered: {}", by_corp.len());
|
||||||
println!(" Brands per corp: min={} max={}", min_per_corp, max_per_corp);
|
println!(
|
||||||
|
" Brands per corp: min={} max={}",
|
||||||
|
min_per_corp, max_per_corp
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -628,7 +632,11 @@ fn main() {
|
|||||||
println!(" [2/5] Loading corporations and commodities...");
|
println!(" [2/5] Loading corporations and commodities...");
|
||||||
let corps = load_corps(&conn);
|
let corps = load_corps(&conn);
|
||||||
let valid_commodities = load_valid_commodity_ids(&conn);
|
let valid_commodities = load_valid_commodity_ids(&conn);
|
||||||
println!(" {} corps, {} valid commodities", corps.len(), valid_commodities.len());
|
println!(
|
||||||
|
" {} corps, {} valid commodities",
|
||||||
|
corps.len(),
|
||||||
|
valid_commodities.len()
|
||||||
|
);
|
||||||
|
|
||||||
// Generate brand pairs
|
// Generate brand pairs
|
||||||
println!(" [3/5] Generating brand pairs...");
|
println!(" [3/5] Generating brand pairs...");
|
||||||
@@ -640,12 +648,24 @@ fn main() {
|
|||||||
|
|
||||||
for (template_key, template) in &templates {
|
for (template_key, template) in &templates {
|
||||||
for corp in &corps {
|
for corp in &corps {
|
||||||
if !corp_eligible(corp, &template.scale_tier, parse_template_corridor(&template.naming_pattern)) {
|
if !corp_eligible(
|
||||||
|
corp,
|
||||||
|
&template.scale_tier,
|
||||||
|
parse_template_corridor(&template.naming_pattern),
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let halo_bid = halo_id(&corp.corp_id, &template.archetype_group, &template.scale_tier);
|
let halo_bid = halo_id(
|
||||||
let vol_bid = volume_id(&corp.corp_id, &template.archetype_group, &template.scale_tier);
|
&corp.corp_id,
|
||||||
|
&template.archetype_group,
|
||||||
|
&template.scale_tier,
|
||||||
|
);
|
||||||
|
let vol_bid = volume_id(
|
||||||
|
&corp.corp_id,
|
||||||
|
&template.archetype_group,
|
||||||
|
&template.scale_tier,
|
||||||
|
);
|
||||||
|
|
||||||
// Skip if IDs already generated (name-collision guard)
|
// Skip if IDs already generated (name-collision guard)
|
||||||
if seen_ids.contains(&halo_bid) || seen_ids.contains(&vol_bid) {
|
if seen_ids.contains(&halo_bid) || seen_ids.contains(&vol_bid) {
|
||||||
@@ -653,7 +673,9 @@ fn main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pair) = generate_pair(&mut rng, corp, template_key, template, &valid_commodities) {
|
if let Some(pair) =
|
||||||
|
generate_pair(&mut rng, corp, template_key, template, &valid_commodities)
|
||||||
|
{
|
||||||
seen_ids.insert(halo_bid);
|
seen_ids.insert(halo_bid);
|
||||||
seen_ids.insert(vol_bid);
|
seen_ids.insert(vol_bid);
|
||||||
products.push(pair.halo);
|
products.push(pair.halo);
|
||||||
@@ -664,7 +686,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(" {} brand_products generated ({} skipped)", products.len(), skipped);
|
println!(
|
||||||
|
" {} brand_products generated ({} skipped)",
|
||||||
|
products.len(),
|
||||||
|
skipped
|
||||||
|
);
|
||||||
|
|
||||||
// Gap-fill: if under target, add more by re-applying reach_wide templates
|
// Gap-fill: if under target, add more by re-applying reach_wide templates
|
||||||
// with a counter suffix to avoid ID collisions.
|
// with a counter suffix to avoid ID collisions.
|
||||||
@@ -679,7 +705,8 @@ fn main() {
|
|||||||
let mut template_idx = 0usize;
|
let mut template_idx = 0usize;
|
||||||
|
|
||||||
while products.len() < cli.min_brands && !reach_wide_templates.is_empty() {
|
while products.len() < cli.min_brands && !reach_wide_templates.is_empty() {
|
||||||
let (template_key, template) = reach_wide_templates[template_idx % reach_wide_templates.len()];
|
let (template_key, template) =
|
||||||
|
reach_wide_templates[template_idx % reach_wide_templates.len()];
|
||||||
let corp = &corps[counter % corps.len()];
|
let corp = &corps[counter % corps.len()];
|
||||||
|
|
||||||
counter += 1;
|
counter += 1;
|
||||||
@@ -704,7 +731,9 @@ fn main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pair) = generate_pair(&mut rng, corp, template_key, template, &valid_commodities) {
|
if let Some(pair) =
|
||||||
|
generate_pair(&mut rng, corp, template_key, template, &valid_commodities)
|
||||||
|
{
|
||||||
let mut halo = pair.halo;
|
let mut halo = pair.halo;
|
||||||
let mut volume = pair.volume;
|
let mut volume = pair.volume;
|
||||||
halo.brand_product_id = halo_bid.clone();
|
halo.brand_product_id = halo_bid.clone();
|
||||||
@@ -713,8 +742,12 @@ fn main() {
|
|||||||
|
|
||||||
let mut inputs_halo = pair.inputs_halo;
|
let mut inputs_halo = pair.inputs_halo;
|
||||||
let mut inputs_volume = pair.inputs_volume;
|
let mut inputs_volume = pair.inputs_volume;
|
||||||
for i in &mut inputs_halo { i.brand_product_id = halo_bid.clone(); }
|
for i in &mut inputs_halo {
|
||||||
for i in &mut inputs_volume { i.brand_product_id = vol_bid.clone(); }
|
i.brand_product_id = halo_bid.clone();
|
||||||
|
}
|
||||||
|
for i in &mut inputs_volume {
|
||||||
|
i.brand_product_id = vol_bid.clone();
|
||||||
|
}
|
||||||
|
|
||||||
seen_ids.insert(halo_bid);
|
seen_ids.insert(halo_bid);
|
||||||
seen_ids.insert(vol_bid);
|
seen_ids.insert(vol_bid);
|
||||||
@@ -741,7 +774,11 @@ fn main() {
|
|||||||
// Write output
|
// Write output
|
||||||
println!("\n [5/5] Writing output...");
|
println!("\n [5/5] Writing output...");
|
||||||
write_output(&output_path, &products, &inputs);
|
write_output(&output_path, &products, &inputs);
|
||||||
println!(" {} brand_products, {} brand_inputs", products.len(), inputs.len());
|
println!(
|
||||||
|
" {} brand_products, {} brand_inputs",
|
||||||
|
products.len(),
|
||||||
|
inputs.len()
|
||||||
|
);
|
||||||
println!(" Output: {}", output_path.display());
|
println!(" Output: {}", output_path.display());
|
||||||
println!(" Done.\n");
|
println!(" Done.\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,60 +13,321 @@ use rand_chacha::ChaCha8Rng;
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const CORE_NAMES: &[&str] = &[
|
const CORE_NAMES: &[&str] = &[
|
||||||
"Alvarez", "Benoit", "Carvalho", "Durand", "Eriksen", "Fournier", "Gao", "Hartmann",
|
"Alvarez",
|
||||||
"Ishida", "Johansson", "Kirchner", "Lemaire", "Moreau", "Nakamura", "Olsson", "Pelletier",
|
"Benoit",
|
||||||
"Richter", "Saito", "Torres", "Ueda", "Vasquez", "Werner", "Xu", "Yamada", "Zhou",
|
"Carvalho",
|
||||||
"Andersen", "Beaumont", "Costa", "Delacroix", "Engel", "Fujita", "Gutierrez", "Hayashi",
|
"Durand",
|
||||||
"Ibarra", "Jensen", "Klein", "Laurent", "Mercier", "Novak", "Ortiz", "Park", "Reuter",
|
"Eriksen",
|
||||||
"Suzuki", "Takahashi", "Ulrich", "Valentin", "Wagner", "Xie", "Yilmaz", "Zhang",
|
"Fournier",
|
||||||
|
"Gao",
|
||||||
|
"Hartmann",
|
||||||
|
"Ishida",
|
||||||
|
"Johansson",
|
||||||
|
"Kirchner",
|
||||||
|
"Lemaire",
|
||||||
|
"Moreau",
|
||||||
|
"Nakamura",
|
||||||
|
"Olsson",
|
||||||
|
"Pelletier",
|
||||||
|
"Richter",
|
||||||
|
"Saito",
|
||||||
|
"Torres",
|
||||||
|
"Ueda",
|
||||||
|
"Vasquez",
|
||||||
|
"Werner",
|
||||||
|
"Xu",
|
||||||
|
"Yamada",
|
||||||
|
"Zhou",
|
||||||
|
"Andersen",
|
||||||
|
"Beaumont",
|
||||||
|
"Costa",
|
||||||
|
"Delacroix",
|
||||||
|
"Engel",
|
||||||
|
"Fujita",
|
||||||
|
"Gutierrez",
|
||||||
|
"Hayashi",
|
||||||
|
"Ibarra",
|
||||||
|
"Jensen",
|
||||||
|
"Klein",
|
||||||
|
"Laurent",
|
||||||
|
"Mercier",
|
||||||
|
"Novak",
|
||||||
|
"Ortiz",
|
||||||
|
"Park",
|
||||||
|
"Reuter",
|
||||||
|
"Suzuki",
|
||||||
|
"Takahashi",
|
||||||
|
"Ulrich",
|
||||||
|
"Valentin",
|
||||||
|
"Wagner",
|
||||||
|
"Xie",
|
||||||
|
"Yilmaz",
|
||||||
|
"Zhang",
|
||||||
];
|
];
|
||||||
|
|
||||||
const NORTH_REACH_NAMES: &[&str] = &[
|
const NORTH_REACH_NAMES: &[&str] = &[
|
||||||
"Andersson", "Bjornsson", "Calloway", "Dalsgaard", "Eklund", "Falk", "Grimstad", "Hedlund",
|
"Andersson",
|
||||||
"Ivarsson", "Jonasson", "Kirkpatrick", "Lindqvist", "MacLeod", "Nordstrom", "Olafsson",
|
"Bjornsson",
|
||||||
"Pettersson", "Rehn", "Strandberg", "Thorsen", "Ulvskog", "Vikstrom", "Wahlberg", "Aberg",
|
"Calloway",
|
||||||
"Berglund", "Carlsen", "Dalgaard", "Engstrom", "Forsell", "Gustafsson", "Halvorsen",
|
"Dalsgaard",
|
||||||
"Ingvarsson", "Jansson", "Knudsen", "Lundin", "MacPherson", "Nylund", "Ostergaard",
|
"Eklund",
|
||||||
"Palsson", "Rasmussen", "Sjoberg", "Toft", "Ulfsson", "Vestergaard", "Wiklund", "Aasen",
|
"Falk",
|
||||||
"Brannstrom", "Dahl", "Eide", "Friberg", "Gren",
|
"Grimstad",
|
||||||
|
"Hedlund",
|
||||||
|
"Ivarsson",
|
||||||
|
"Jonasson",
|
||||||
|
"Kirkpatrick",
|
||||||
|
"Lindqvist",
|
||||||
|
"MacLeod",
|
||||||
|
"Nordstrom",
|
||||||
|
"Olafsson",
|
||||||
|
"Pettersson",
|
||||||
|
"Rehn",
|
||||||
|
"Strandberg",
|
||||||
|
"Thorsen",
|
||||||
|
"Ulvskog",
|
||||||
|
"Vikstrom",
|
||||||
|
"Wahlberg",
|
||||||
|
"Aberg",
|
||||||
|
"Berglund",
|
||||||
|
"Carlsen",
|
||||||
|
"Dalgaard",
|
||||||
|
"Engstrom",
|
||||||
|
"Forsell",
|
||||||
|
"Gustafsson",
|
||||||
|
"Halvorsen",
|
||||||
|
"Ingvarsson",
|
||||||
|
"Jansson",
|
||||||
|
"Knudsen",
|
||||||
|
"Lundin",
|
||||||
|
"MacPherson",
|
||||||
|
"Nylund",
|
||||||
|
"Ostergaard",
|
||||||
|
"Palsson",
|
||||||
|
"Rasmussen",
|
||||||
|
"Sjoberg",
|
||||||
|
"Toft",
|
||||||
|
"Ulfsson",
|
||||||
|
"Vestergaard",
|
||||||
|
"Wiklund",
|
||||||
|
"Aasen",
|
||||||
|
"Brannstrom",
|
||||||
|
"Dahl",
|
||||||
|
"Eide",
|
||||||
|
"Friberg",
|
||||||
|
"Gren",
|
||||||
];
|
];
|
||||||
|
|
||||||
const SOUTH_REACH_NAMES: &[&str] = &[
|
const SOUTH_REACH_NAMES: &[&str] = &[
|
||||||
"Adamski", "Baranov", "Chernov", "Dubois", "Egorov", "Filipov", "Gromov", "Horvat",
|
"Adamski",
|
||||||
"Ivanova", "Jankovic", "Kowalski", "Lazarev", "Morozov", "Novikov", "Ostrowski", "Petrov",
|
"Baranov",
|
||||||
"Reznik", "Sokolov", "Tkachenko", "Uvarov", "Volkov", "Wojcik", "Yakimov", "Zheng",
|
"Chernov",
|
||||||
"Babic", "Chernyshev", "Dragunov", "Fedorov", "Grushevsky", "Havel", "Ito", "Jovanovic",
|
"Dubois",
|
||||||
"Katsaros", "Lebedev", "Mazur", "Nemec", "Ochoa", "Popov", "Radic", "Smirnov", "Tanaka",
|
"Egorov",
|
||||||
"Urasawa", "Vasiliev", "Watanabe", "Xiang", "Yegorov", "Zaytsev", "Borysko", "Chen",
|
"Filipov",
|
||||||
|
"Gromov",
|
||||||
|
"Horvat",
|
||||||
|
"Ivanova",
|
||||||
|
"Jankovic",
|
||||||
|
"Kowalski",
|
||||||
|
"Lazarev",
|
||||||
|
"Morozov",
|
||||||
|
"Novikov",
|
||||||
|
"Ostrowski",
|
||||||
|
"Petrov",
|
||||||
|
"Reznik",
|
||||||
|
"Sokolov",
|
||||||
|
"Tkachenko",
|
||||||
|
"Uvarov",
|
||||||
|
"Volkov",
|
||||||
|
"Wojcik",
|
||||||
|
"Yakimov",
|
||||||
|
"Zheng",
|
||||||
|
"Babic",
|
||||||
|
"Chernyshev",
|
||||||
|
"Dragunov",
|
||||||
|
"Fedorov",
|
||||||
|
"Grushevsky",
|
||||||
|
"Havel",
|
||||||
|
"Ito",
|
||||||
|
"Jovanovic",
|
||||||
|
"Katsaros",
|
||||||
|
"Lebedev",
|
||||||
|
"Mazur",
|
||||||
|
"Nemec",
|
||||||
|
"Ochoa",
|
||||||
|
"Popov",
|
||||||
|
"Radic",
|
||||||
|
"Smirnov",
|
||||||
|
"Tanaka",
|
||||||
|
"Urasawa",
|
||||||
|
"Vasiliev",
|
||||||
|
"Watanabe",
|
||||||
|
"Xiang",
|
||||||
|
"Yegorov",
|
||||||
|
"Zaytsev",
|
||||||
|
"Borysko",
|
||||||
|
"Chen",
|
||||||
"Dimitrov",
|
"Dimitrov",
|
||||||
];
|
];
|
||||||
|
|
||||||
const WEST_REACH_NAMES: &[&str] = &[
|
const WEST_REACH_NAMES: &[&str] = &[
|
||||||
"Albrecht", "Baumann", "Christensen", "Dietrich", "Eisenberg", "Fischer", "Gruber",
|
"Albrecht",
|
||||||
"Hoffmann", "Ingolstadt", "Jaeger", "Kessler", "Lehmann", "Mueller", "Neumann", "Obermann",
|
"Baumann",
|
||||||
"Pfeiffer", "Quandt", "Roth", "Schaefer", "Thiel", "Urban", "Vogt", "Weidenfeld",
|
"Christensen",
|
||||||
"Ziegler", "Becker", "Claussen", "Dorfmann", "Eberhardt", "Fleischer", "Gerstner", "Haber",
|
"Dietrich",
|
||||||
"Imhof", "Jung", "Kraemer", "Linden", "Metzger", "Niedermann", "Opitz", "Preuss", "Raabe",
|
"Eisenberg",
|
||||||
"Steinbach", "Trautmann", "Unger", "Vollmer", "Winterberg", "Zahn", "Auerbach", "Bruckner",
|
"Fischer",
|
||||||
"Dahlem", "Eckhardt",
|
"Gruber",
|
||||||
|
"Hoffmann",
|
||||||
|
"Ingolstadt",
|
||||||
|
"Jaeger",
|
||||||
|
"Kessler",
|
||||||
|
"Lehmann",
|
||||||
|
"Mueller",
|
||||||
|
"Neumann",
|
||||||
|
"Obermann",
|
||||||
|
"Pfeiffer",
|
||||||
|
"Quandt",
|
||||||
|
"Roth",
|
||||||
|
"Schaefer",
|
||||||
|
"Thiel",
|
||||||
|
"Urban",
|
||||||
|
"Vogt",
|
||||||
|
"Weidenfeld",
|
||||||
|
"Ziegler",
|
||||||
|
"Becker",
|
||||||
|
"Claussen",
|
||||||
|
"Dorfmann",
|
||||||
|
"Eberhardt",
|
||||||
|
"Fleischer",
|
||||||
|
"Gerstner",
|
||||||
|
"Haber",
|
||||||
|
"Imhof",
|
||||||
|
"Jung",
|
||||||
|
"Kraemer",
|
||||||
|
"Linden",
|
||||||
|
"Metzger",
|
||||||
|
"Niedermann",
|
||||||
|
"Opitz",
|
||||||
|
"Preuss",
|
||||||
|
"Raabe",
|
||||||
|
"Steinbach",
|
||||||
|
"Trautmann",
|
||||||
|
"Unger",
|
||||||
|
"Vollmer",
|
||||||
|
"Winterberg",
|
||||||
|
"Zahn",
|
||||||
|
"Auerbach",
|
||||||
|
"Bruckner",
|
||||||
|
"Dahlem",
|
||||||
|
"Eckhardt",
|
||||||
];
|
];
|
||||||
|
|
||||||
const EAST_REACH_NAMES: &[&str] = &[
|
const EAST_REACH_NAMES: &[&str] = &[
|
||||||
"Aquino", "Bautista", "Cruz", "Dalisay", "Espiritu", "Flores", "Garcia", "Hernandez",
|
"Aquino",
|
||||||
"Ilagan", "Jeon", "Kim", "Lim", "Magalang", "Navarro", "Ocampo", "Park", "Quijano",
|
"Bautista",
|
||||||
"Reyes", "Santos", "Tan", "Uy", "Villanueva", "Wong", "Yoo", "Aguilar", "Buenaventura",
|
"Cruz",
|
||||||
"Castillo", "Dizon", "Enriquez", "Fernandez", "Gonzales", "Hwang", "Ignacio", "Jeong",
|
"Dalisay",
|
||||||
"Kwon", "Lee", "Marasigan", "Nakamura", "Oh", "Perez", "Ramos", "Son", "Tolentino",
|
"Espiritu",
|
||||||
"Umali", "Valdez", "Yun", "Zamora", "Baek", "Choi", "Dela Cruz",
|
"Flores",
|
||||||
|
"Garcia",
|
||||||
|
"Hernandez",
|
||||||
|
"Ilagan",
|
||||||
|
"Jeon",
|
||||||
|
"Kim",
|
||||||
|
"Lim",
|
||||||
|
"Magalang",
|
||||||
|
"Navarro",
|
||||||
|
"Ocampo",
|
||||||
|
"Park",
|
||||||
|
"Quijano",
|
||||||
|
"Reyes",
|
||||||
|
"Santos",
|
||||||
|
"Tan",
|
||||||
|
"Uy",
|
||||||
|
"Villanueva",
|
||||||
|
"Wong",
|
||||||
|
"Yoo",
|
||||||
|
"Aguilar",
|
||||||
|
"Buenaventura",
|
||||||
|
"Castillo",
|
||||||
|
"Dizon",
|
||||||
|
"Enriquez",
|
||||||
|
"Fernandez",
|
||||||
|
"Gonzales",
|
||||||
|
"Hwang",
|
||||||
|
"Ignacio",
|
||||||
|
"Jeong",
|
||||||
|
"Kwon",
|
||||||
|
"Lee",
|
||||||
|
"Marasigan",
|
||||||
|
"Nakamura",
|
||||||
|
"Oh",
|
||||||
|
"Perez",
|
||||||
|
"Ramos",
|
||||||
|
"Son",
|
||||||
|
"Tolentino",
|
||||||
|
"Umali",
|
||||||
|
"Valdez",
|
||||||
|
"Yun",
|
||||||
|
"Zamora",
|
||||||
|
"Baek",
|
||||||
|
"Choi",
|
||||||
|
"Dela Cruz",
|
||||||
];
|
];
|
||||||
|
|
||||||
const FRONTIER_NAMES: &[&str] = &[
|
const FRONTIER_NAMES: &[&str] = &[
|
||||||
"Adeyemi", "Bergstrom", "Chandra", "Duval", "Emeka", "Fonseca", "Gupta", "Hassan",
|
"Adeyemi",
|
||||||
"Ibrahim", "Jansson", "Kovac", "Liu", "Martinez", "Nkosi", "Okafor", "Patel", "Quinn",
|
"Bergstrom",
|
||||||
"Rodriguez", "Sousa", "Thorne", "Uddin", "Varga", "Wu", "Xiong", "Yoshida", "Zhao",
|
"Chandra",
|
||||||
"Abara", "Beaumont", "Cardenas", "Doyle", "Ekwueme", "Ferreira", "Gomes", "Henriksen",
|
"Duval",
|
||||||
"Idris", "Juma", "Kato", "Larsen", "Morales", "Ndlovu", "Osei", "Petrov", "Ruiz",
|
"Emeka",
|
||||||
"Singh", "Tavares", "Uchida", "Volkov", "Wang", "Yang", "Zaman",
|
"Fonseca",
|
||||||
|
"Gupta",
|
||||||
|
"Hassan",
|
||||||
|
"Ibrahim",
|
||||||
|
"Jansson",
|
||||||
|
"Kovac",
|
||||||
|
"Liu",
|
||||||
|
"Martinez",
|
||||||
|
"Nkosi",
|
||||||
|
"Okafor",
|
||||||
|
"Patel",
|
||||||
|
"Quinn",
|
||||||
|
"Rodriguez",
|
||||||
|
"Sousa",
|
||||||
|
"Thorne",
|
||||||
|
"Uddin",
|
||||||
|
"Varga",
|
||||||
|
"Wu",
|
||||||
|
"Xiong",
|
||||||
|
"Yoshida",
|
||||||
|
"Zhao",
|
||||||
|
"Abara",
|
||||||
|
"Beaumont",
|
||||||
|
"Cardenas",
|
||||||
|
"Doyle",
|
||||||
|
"Ekwueme",
|
||||||
|
"Ferreira",
|
||||||
|
"Gomes",
|
||||||
|
"Henriksen",
|
||||||
|
"Idris",
|
||||||
|
"Juma",
|
||||||
|
"Kato",
|
||||||
|
"Larsen",
|
||||||
|
"Morales",
|
||||||
|
"Ndlovu",
|
||||||
|
"Osei",
|
||||||
|
"Petrov",
|
||||||
|
"Ruiz",
|
||||||
|
"Singh",
|
||||||
|
"Tavares",
|
||||||
|
"Uchida",
|
||||||
|
"Volkov",
|
||||||
|
"Wang",
|
||||||
|
"Yang",
|
||||||
|
"Zaman",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn names_for_corridor(corridor: &str) -> &'static [&'static str] {
|
fn names_for_corridor(corridor: &str) -> &'static [&'static str] {
|
||||||
@@ -85,83 +346,206 @@ fn names_for_corridor(corridor: &str) -> &'static [&'static str] {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const TERROIR_HALO: &[&str] = &[
|
const TERROIR_HALO: &[&str] = &[
|
||||||
"Reserve", "Single", "Estate", "Vintage", "Heritage", "Grand", "Limited", "Prestige",
|
"Reserve",
|
||||||
"Signature", "Cellar", "Select", "Cru", "Premier", "Old", "Aged",
|
"Single",
|
||||||
|
"Estate",
|
||||||
|
"Vintage",
|
||||||
|
"Heritage",
|
||||||
|
"Grand",
|
||||||
|
"Limited",
|
||||||
|
"Prestige",
|
||||||
|
"Signature",
|
||||||
|
"Cellar",
|
||||||
|
"Select",
|
||||||
|
"Cru",
|
||||||
|
"Premier",
|
||||||
|
"Old",
|
||||||
|
"Aged",
|
||||||
];
|
];
|
||||||
|
|
||||||
const TERROIR_VOLUME: &[&str] = &[
|
const TERROIR_VOLUME: &[&str] = &[
|
||||||
"Standard", "Export", "Blend", "Classic", "Field", "Ordinary", "Running", "Table",
|
"Standard", "Export", "Blend", "Classic", "Field", "Ordinary", "Running", "Table", "Regular",
|
||||||
"Regular", "Common", "House", "Station", "Corridor", "Transit",
|
"Common", "House", "Station", "Corridor", "Transit",
|
||||||
];
|
];
|
||||||
|
|
||||||
const HERITAGE_CRAFT_HALO: &[&str] = &[
|
const HERITAGE_CRAFT_HALO: &[&str] = &[
|
||||||
"Heritage", "Limited", "Artisan", "Master", "Guild", "Prestige", "Premium", "Classic",
|
"Heritage",
|
||||||
"Signature", "Original", "Bespoke", "Traditional", "First",
|
"Limited",
|
||||||
|
"Artisan",
|
||||||
|
"Master",
|
||||||
|
"Guild",
|
||||||
|
"Prestige",
|
||||||
|
"Premium",
|
||||||
|
"Classic",
|
||||||
|
"Signature",
|
||||||
|
"Original",
|
||||||
|
"Bespoke",
|
||||||
|
"Traditional",
|
||||||
|
"First",
|
||||||
];
|
];
|
||||||
|
|
||||||
const HERITAGE_CRAFT_VOLUME: &[&str] = &[
|
const HERITAGE_CRAFT_VOLUME: &[&str] = &[
|
||||||
"Standard", "Classic", "Working", "Everyday", "Regular", "Plain", "Field", "Grade",
|
"Standard", "Classic", "Working", "Everyday", "Regular", "Plain", "Field", "Grade", "Basic",
|
||||||
"Basic", "Workshop", "Studio", "Common",
|
"Workshop", "Studio", "Common",
|
||||||
];
|
];
|
||||||
|
|
||||||
const TECH_PREMIUM_HALO: &[&str] = &[
|
const TECH_PREMIUM_HALO: &[&str] = &[
|
||||||
"Elite", "Pro", "Advanced", "Precision", "Superior", "Grand", "Signature", "First",
|
"Elite",
|
||||||
"Prime", "Expert", "Master", "Apex", "Summit",
|
"Pro",
|
||||||
|
"Advanced",
|
||||||
|
"Precision",
|
||||||
|
"Superior",
|
||||||
|
"Grand",
|
||||||
|
"Signature",
|
||||||
|
"First",
|
||||||
|
"Prime",
|
||||||
|
"Expert",
|
||||||
|
"Master",
|
||||||
|
"Apex",
|
||||||
|
"Summit",
|
||||||
];
|
];
|
||||||
|
|
||||||
const TECH_PREMIUM_VOLUME: &[&str] = &[
|
const TECH_PREMIUM_VOLUME: &[&str] = &[
|
||||||
"Standard", "Series", "Base", "Classic", "Regular", "Field", "Grade", "Value",
|
"Standard",
|
||||||
"Essential", "Core", "Basic",
|
"Series",
|
||||||
|
"Base",
|
||||||
|
"Classic",
|
||||||
|
"Regular",
|
||||||
|
"Field",
|
||||||
|
"Grade",
|
||||||
|
"Value",
|
||||||
|
"Essential",
|
||||||
|
"Core",
|
||||||
|
"Basic",
|
||||||
];
|
];
|
||||||
|
|
||||||
const CULTURAL_HALO: &[&str] = &[
|
const CULTURAL_HALO: &[&str] = &[
|
||||||
"Archive", "Heritage", "Classic", "Definitive", "Prestige", "Limited", "Master",
|
"Archive",
|
||||||
"Collected", "Curated", "Canonical", "Flagship", "Grand",
|
"Heritage",
|
||||||
|
"Classic",
|
||||||
|
"Definitive",
|
||||||
|
"Prestige",
|
||||||
|
"Limited",
|
||||||
|
"Master",
|
||||||
|
"Collected",
|
||||||
|
"Curated",
|
||||||
|
"Canonical",
|
||||||
|
"Flagship",
|
||||||
|
"Grand",
|
||||||
];
|
];
|
||||||
|
|
||||||
const CULTURAL_VOLUME: &[&str] = &[
|
const CULTURAL_VOLUME: &[&str] = &[
|
||||||
"Standard", "Classic", "Essential", "Value", "Regular", "Base", "Field",
|
"Standard",
|
||||||
"Running", "Everyday", "Popular",
|
"Classic",
|
||||||
|
"Essential",
|
||||||
|
"Value",
|
||||||
|
"Regular",
|
||||||
|
"Base",
|
||||||
|
"Field",
|
||||||
|
"Running",
|
||||||
|
"Everyday",
|
||||||
|
"Popular",
|
||||||
];
|
];
|
||||||
|
|
||||||
const SERVICE_PREMIUM_HALO: &[&str] = &[
|
const SERVICE_PREMIUM_HALO: &[&str] = &[
|
||||||
"Premier", "Elite", "Priority", "Signature", "Prestige", "Grand", "First", "Select",
|
"Premier",
|
||||||
"Platinum", "Gold", "Senior", "Executive",
|
"Elite",
|
||||||
|
"Priority",
|
||||||
|
"Signature",
|
||||||
|
"Prestige",
|
||||||
|
"Grand",
|
||||||
|
"First",
|
||||||
|
"Select",
|
||||||
|
"Platinum",
|
||||||
|
"Gold",
|
||||||
|
"Senior",
|
||||||
|
"Executive",
|
||||||
];
|
];
|
||||||
|
|
||||||
const SERVICE_PREMIUM_VOLUME: &[&str] = &[
|
const SERVICE_PREMIUM_VOLUME: &[&str] = &[
|
||||||
"Standard", "Basic", "Classic", "Regular", "Field", "Value", "General", "Common",
|
"Standard",
|
||||||
"Ordinary", "Essential",
|
"Basic",
|
||||||
|
"Classic",
|
||||||
|
"Regular",
|
||||||
|
"Field",
|
||||||
|
"Value",
|
||||||
|
"General",
|
||||||
|
"Common",
|
||||||
|
"Ordinary",
|
||||||
|
"Essential",
|
||||||
];
|
];
|
||||||
|
|
||||||
const COMMODITY_BRANDED_HALO: &[&str] = &[
|
const COMMODITY_BRANDED_HALO: &[&str] = &[
|
||||||
"Original", "Select", "Premium", "Reserve", "Classic", "Heritage", "Signature",
|
"Original",
|
||||||
"Superior", "First", "Grade", "Certified",
|
"Select",
|
||||||
|
"Premium",
|
||||||
|
"Reserve",
|
||||||
|
"Classic",
|
||||||
|
"Heritage",
|
||||||
|
"Signature",
|
||||||
|
"Superior",
|
||||||
|
"First",
|
||||||
|
"Grade",
|
||||||
|
"Certified",
|
||||||
];
|
];
|
||||||
|
|
||||||
const COMMODITY_BRANDED_VOLUME: &[&str] = &[
|
const COMMODITY_BRANDED_VOLUME: &[&str] = &[
|
||||||
"Standard", "Basic", "Regular", "Field", "Value", "Economy", "Bulk", "Run",
|
"Standard", "Basic", "Regular", "Field", "Value", "Economy", "Bulk", "Run", "Common", "Grade",
|
||||||
"Common", "Grade", "Plain",
|
"Plain",
|
||||||
];
|
];
|
||||||
|
|
||||||
const DESIGN_HERITAGE_HALO: &[&str] = &[
|
const DESIGN_HERITAGE_HALO: &[&str] = &[
|
||||||
"Heritage", "Limited", "Prestige", "Grand", "Signature", "Edition", "Series",
|
"Heritage",
|
||||||
"Classic", "Archive", "Collector", "Retrospective",
|
"Limited",
|
||||||
|
"Prestige",
|
||||||
|
"Grand",
|
||||||
|
"Signature",
|
||||||
|
"Edition",
|
||||||
|
"Series",
|
||||||
|
"Classic",
|
||||||
|
"Archive",
|
||||||
|
"Collector",
|
||||||
|
"Retrospective",
|
||||||
];
|
];
|
||||||
|
|
||||||
const DESIGN_HERITAGE_VOLUME: &[&str] = &[
|
const DESIGN_HERITAGE_VOLUME: &[&str] = &[
|
||||||
"Standard", "Classic", "Regular", "Field", "Value", "Base", "Essential",
|
"Standard",
|
||||||
"Running", "Contemporary", "Current",
|
"Classic",
|
||||||
|
"Regular",
|
||||||
|
"Field",
|
||||||
|
"Value",
|
||||||
|
"Base",
|
||||||
|
"Essential",
|
||||||
|
"Running",
|
||||||
|
"Contemporary",
|
||||||
|
"Current",
|
||||||
];
|
];
|
||||||
|
|
||||||
const PLATFORM_CATALOGUE_HALO: &[&str] = &[
|
const PLATFORM_CATALOGUE_HALO: &[&str] = &[
|
||||||
"Premium", "Pro", "Plus", "Elite", "Advanced", "Signature", "Select", "Grand",
|
"Premium",
|
||||||
"Unlimited", "Complete", "Full",
|
"Pro",
|
||||||
|
"Plus",
|
||||||
|
"Elite",
|
||||||
|
"Advanced",
|
||||||
|
"Signature",
|
||||||
|
"Select",
|
||||||
|
"Grand",
|
||||||
|
"Unlimited",
|
||||||
|
"Complete",
|
||||||
|
"Full",
|
||||||
];
|
];
|
||||||
|
|
||||||
const PLATFORM_CATALOGUE_VOLUME: &[&str] = &[
|
const PLATFORM_CATALOGUE_VOLUME: &[&str] = &[
|
||||||
"Standard", "Basic", "Classic", "Regular", "Field", "Value", "Entry", "Lite",
|
"Standard",
|
||||||
"Essential", "Free",
|
"Basic",
|
||||||
|
"Classic",
|
||||||
|
"Regular",
|
||||||
|
"Field",
|
||||||
|
"Value",
|
||||||
|
"Entry",
|
||||||
|
"Lite",
|
||||||
|
"Essential",
|
||||||
|
"Free",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn halo_descriptors(brand_category: &str) -> &'static [&'static str] {
|
fn halo_descriptors(brand_category: &str) -> &'static [&'static str] {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use bevy_app::prelude::*;
|
|||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
|
|
||||||
use crate::bridge::types::{BookmarkCatalog, BookmarkWire, CareerKindWire, SnapshotBuffer};
|
use crate::bridge::types::{BookmarkCatalog, BookmarkWire, CareerKindWire, SnapshotBuffer};
|
||||||
use crate::knowledge::{CultureResolver, resolve_culture};
|
use crate::knowledge::{resolve_culture, CultureResolver};
|
||||||
|
|
||||||
/// Immutable registry of bookmark definitions.
|
/// Immutable registry of bookmark definitions.
|
||||||
///
|
///
|
||||||
@@ -190,7 +190,10 @@ mod tests {
|
|||||||
assert_eq!(wire.id, "tycoon");
|
assert_eq!(wire.id, "tycoon");
|
||||||
assert_eq!(wire.career, CareerKindWire::Tycoon);
|
assert_eq!(wire.career, CareerKindWire::Tycoon);
|
||||||
assert!(!wire.default_location.is_empty());
|
assert!(!wire.default_location.is_empty());
|
||||||
assert_eq!(wire.allowed_locations.len(), wire.allowed_locations_cultures.len());
|
assert_eq!(
|
||||||
|
wire.allowed_locations.len(),
|
||||||
|
wire.allowed_locations_cultures.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -216,6 +219,8 @@ mod tests {
|
|||||||
let r = make_registry();
|
let r = make_registry();
|
||||||
let defn = r.get("tycoon").unwrap();
|
let defn = r.get("tycoon").unwrap();
|
||||||
assert!(defn.allowed_locations.contains(&"GJ 35".to_string()));
|
assert!(defn.allowed_locations.contains(&"GJ 35".to_string()));
|
||||||
assert!(!defn.allowed_locations.contains(&"Unknown System".to_string()));
|
assert!(!defn
|
||||||
|
.allowed_locations
|
||||||
|
.contains(&"Unknown System".to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ use crate::bridge::types::{
|
|||||||
};
|
};
|
||||||
use crate::knowledge::EntityRegistry;
|
use crate::knowledge::EntityRegistry;
|
||||||
use crate::npc::Npc;
|
use crate::npc::Npc;
|
||||||
use crate::simulation::conversation::NpcName;
|
|
||||||
use crate::simulation::economy::{EconSimResource, EconStateResource};
|
use crate::simulation::economy::{EconSimResource, EconStateResource};
|
||||||
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
||||||
|
use crate::simulation::npc_components::NpcName;
|
||||||
use crate::simulation::tier::ActiveSim;
|
use crate::simulation::tier::ActiveSim;
|
||||||
use crate::simulation::time::SimulationTime;
|
use crate::simulation::time::SimulationTime;
|
||||||
use crate::simulation::triangle::TriangleState;
|
use crate::simulation::triangle::TriangleState;
|
||||||
|
|||||||
@@ -244,7 +244,6 @@ impl Plugin for BridgePlugin {
|
|||||||
crate::simulation::monologue::trigger_event_monologue
|
crate::simulation::monologue::trigger_event_monologue
|
||||||
.after(crate::simulation::monologue::process_sprint_anomaly_monologue)
|
.after(crate::simulation::monologue::process_sprint_anomaly_monologue)
|
||||||
.after(crate::simulation::sound::collect_sound_events)
|
.after(crate::simulation::sound::collect_sound_events)
|
||||||
.after(crate::simulation::conversation::run_npc_conversations)
|
|
||||||
.after(crate::simulation::dialogue::process_walk_away),
|
.after(crate::simulation::dialogue::process_walk_away),
|
||||||
crate::simulation::monologue::process_contradiction_monologue
|
crate::simulation::monologue::process_contradiction_monologue
|
||||||
.after(crate::simulation::monologue::trigger_event_monologue),
|
.after(crate::simulation::monologue::trigger_event_monologue),
|
||||||
|
|||||||
@@ -304,8 +304,7 @@ mod tests {
|
|||||||
dialogue_response: None,
|
dialogue_response: None,
|
||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
@@ -447,8 +446,7 @@ mod tests {
|
|||||||
dialogue_response: None,
|
dialogue_response: None,
|
||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub use crate::simulation::time::{DayPhase, TickRate};
|
|||||||
/// negotiation is unnecessary. Client should reject snapshots with version !=
|
/// negotiation is unnecessary. Client should reject snapshots with version !=
|
||||||
/// PROTOCOL_VERSION. New fields use #[serde(default)] only during the migration
|
/// PROTOCOL_VERSION. New fields use #[serde(default)] only during the migration
|
||||||
/// period, then the default is removed once both sides are updated.
|
/// period, then the default is removed once both sides are updated.
|
||||||
pub const PROTOCOL_VERSION: u8 = 22;
|
pub const PROTOCOL_VERSION: u8 = 23;
|
||||||
|
|
||||||
/// Handshake message sent as the very first framed message after connection (#555).
|
/// Handshake message sent as the very first framed message after connection (#555).
|
||||||
/// Client reads this before entering the normal tick loop and validates
|
/// Client reads this before entering the normal tick loop and validates
|
||||||
@@ -85,6 +85,7 @@ pub struct StartupMessage {
|
|||||||
/// EconStateQuery PlayerAction variant (#822).
|
/// EconStateQuery PlayerAction variant (#822).
|
||||||
/// v22 adds: bookmark_catalog (#614, D-115/D-117 CK3-style bookmark system),
|
/// v22 adds: bookmark_catalog (#614, D-115/D-117 CK3-style bookmark system),
|
||||||
/// RequestBookmarkCatalog + ConfirmBookmark PlayerAction variants (#614).
|
/// RequestBookmarkCatalog + ConfirmBookmark PlayerAction variants (#614).
|
||||||
|
/// v23 removes: conversation_events, conversation_ended (D-078 scrapped per R-012).
|
||||||
/// Future fields: ambient sound events, HUD state (D-020 expansion).
|
/// Future fields: ambient sound events, HUD state (D-020 expansion).
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ObserverSnapshot {
|
pub struct ObserverSnapshot {
|
||||||
@@ -142,15 +143,6 @@ pub struct ObserverSnapshot {
|
|||||||
/// Empty when no sounds are in range.
|
/// Empty when no sounds are in range.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub sound_events: Vec<crate::simulation::sound::SoundEvent>,
|
pub sound_events: Vec<crate::simulation::sound::SoundEvent>,
|
||||||
/// Overheard NPC-to-NPC conversation lines this tick (#247, D-078).
|
|
||||||
/// Each event carries pre-occluded text — client renders verbatim.
|
|
||||||
/// Empty when no conversations are overheard.
|
|
||||||
#[serde(default)]
|
|
||||||
pub conversation_events: Vec<crate::simulation::conversation::ConversationEvent>,
|
|
||||||
/// Conversations that ended this tick (#247, D-078).
|
|
||||||
/// Client dismisses the passive dialogue panel for these pairs.
|
|
||||||
#[serde(default)]
|
|
||||||
pub conversation_ended: Vec<crate::simulation::conversation::ConversationEndEvent>,
|
|
||||||
/// Follow-mode state for client HUD display (#241).
|
/// Follow-mode state for client HUD display (#241).
|
||||||
/// Present when the player is actively following an NPC.
|
/// Present when the player is actively following an NPC.
|
||||||
/// Client shows follow indicator with distance, LOS, and tension.
|
/// Client shows follow indicator with distance, LOS, and tension.
|
||||||
|
|||||||
@@ -92,7 +92,10 @@ pub struct CultureResolverResource(pub CultureResolver);
|
|||||||
/// - `CultureError::UnknownLocation` — not found in any table.
|
/// - `CultureError::UnknownLocation` — not found in any table.
|
||||||
/// - `CultureError::NoCulture` — found but culture column is NULL (data bug).
|
/// - `CultureError::NoCulture` — found but culture column is NULL (data bug).
|
||||||
/// - `CultureError::Db` — SQLite I/O failure.
|
/// - `CultureError::Db` — SQLite I/O failure.
|
||||||
pub fn resolve_culture(resolver: &CultureResolver, location_id: &str) -> Result<CultureTag, CultureError> {
|
pub fn resolve_culture(
|
||||||
|
resolver: &CultureResolver,
|
||||||
|
location_id: &str,
|
||||||
|
) -> Result<CultureTag, CultureError> {
|
||||||
let conn = resolver
|
let conn = resolver
|
||||||
.conn
|
.conn
|
||||||
.lock()
|
.lock()
|
||||||
@@ -187,8 +190,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn fixture_db() -> CultureResolver {
|
fn fixture_db() -> CultureResolver {
|
||||||
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
let path =
|
||||||
.join("src/knowledge/fixtures/culture_test.db");
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("src/knowledge/fixtures/culture_test.db");
|
||||||
CultureResolver::open(&path).expect("open fixture DB")
|
CultureResolver::open(&path).expect("open fixture DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,8 +260,8 @@ mod tests {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
let path =
|
||||||
.join("src/knowledge/fixtures/culture_test.db");
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("src/knowledge/fixtures/culture_test.db");
|
||||||
let r = Arc::new(CultureResolver::open(&path).expect("open"));
|
let r = Arc::new(CultureResolver::open(&path).expect("open"));
|
||||||
|
|
||||||
let handles: Vec<_> = (0..4)
|
let handles: Vec<_> = (0..4)
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ pub fn process_knowledge_events(
|
|||||||
mut queue: ResMut<KnowledgeEventQueue>,
|
mut queue: ResMut<KnowledgeEventQueue>,
|
||||||
mut contradiction_queue: ResMut<ContradictionDetectedQueue>,
|
mut contradiction_queue: ResMut<ContradictionDetectedQueue>,
|
||||||
registry: Res<EntityRegistry>,
|
registry: Res<EntityRegistry>,
|
||||||
npc_names: Query<&crate::simulation::conversation::NpcName>,
|
npc_names: Query<&crate::simulation::npc_components::NpcName>,
|
||||||
mut knowledge_query: Query<&mut KnowledgeGraph>,
|
mut knowledge_query: Query<&mut KnowledgeGraph>,
|
||||||
) {
|
) {
|
||||||
let events = queue.drain();
|
let events = queue.drain();
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ pub mod registry;
|
|||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub use content_registry::ContentEntityRegistry;
|
pub use content_registry::ContentEntityRegistry;
|
||||||
pub use culture::{CultureError, CultureResolver, CultureResolverResource, CultureTag, resolve_culture};
|
pub use culture::{
|
||||||
|
resolve_culture, CultureError, CultureResolver, CultureResolverResource, CultureTag,
|
||||||
|
};
|
||||||
pub use events::{
|
pub use events::{
|
||||||
ContradictionDetectedEvent, ContradictionDetectedQueue, InteractionType, KnowledgeEvent,
|
ContradictionDetectedEvent, ContradictionDetectedQueue, InteractionType, KnowledgeEvent,
|
||||||
KnowledgeEventQueue, KnowledgeEventType, ProcessedEntityGrant, ProcessedFactGrant,
|
KnowledgeEventQueue, KnowledgeEventType, ProcessedEntityGrant, ProcessedFactGrant,
|
||||||
|
|||||||
+3
-3
@@ -161,7 +161,9 @@ fn main() {
|
|||||||
match settled_reach_server::knowledge::CultureResolver::open(&systems_db_path) {
|
match settled_reach_server::knowledge::CultureResolver::open(&systems_db_path) {
|
||||||
Ok(resolver) => {
|
Ok(resolver) => {
|
||||||
tracing::info!("Culture resolver opened: {:?}", systems_db_path);
|
tracing::info!("Culture resolver opened: {:?}", systems_db_path);
|
||||||
app.insert_resource(settled_reach_server::knowledge::CultureResolverResource(resolver));
|
app.insert_resource(settled_reach_server::knowledge::CultureResolverResource(
|
||||||
|
resolver,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@@ -343,8 +345,6 @@ fn send_panic_error(app: &App, panic_msg: &str) {
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use crate::perception::cognitive_delay::CognitiveDelay;
|
|||||||
use crate::perception::query::{ActivePerceptionMode, VisibilityGeometry};
|
use crate::perception::query::{ActivePerceptionMode, VisibilityGeometry};
|
||||||
use crate::perception::vision_cone::Facing;
|
use crate::perception::vision_cone::Facing;
|
||||||
use crate::simulation::contraband::ScanEventBuffer;
|
use crate::simulation::contraband::ScanEventBuffer;
|
||||||
use crate::simulation::conversation::ConversationEventBuffer;
|
|
||||||
use crate::simulation::dialogue::DialogueResponseBuffer;
|
use crate::simulation::dialogue::DialogueResponseBuffer;
|
||||||
use crate::simulation::examine::ExamineResultBuffer;
|
use crate::simulation::examine::ExamineResultBuffer;
|
||||||
use crate::simulation::follow::FollowTarget;
|
use crate::simulation::follow::FollowTarget;
|
||||||
@@ -84,7 +83,6 @@ pub fn compute_observer_snapshot(
|
|||||||
Option<&CognitiveDelay>,
|
Option<&CognitiveDelay>,
|
||||||
Option<&mut DialogueResponseBuffer>,
|
Option<&mut DialogueResponseBuffer>,
|
||||||
Option<&mut ScanEventBuffer>,
|
Option<&mut ScanEventBuffer>,
|
||||||
Option<&mut ConversationEventBuffer>,
|
|
||||||
Option<&FollowTarget>,
|
Option<&FollowTarget>,
|
||||||
Option<&mut ExamineResultBuffer>,
|
Option<&mut ExamineResultBuffer>,
|
||||||
),
|
),
|
||||||
@@ -121,7 +119,6 @@ pub fn compute_observer_snapshot(
|
|||||||
cognitive_delay_opt,
|
cognitive_delay_opt,
|
||||||
mut dialogue_response_opt,
|
mut dialogue_response_opt,
|
||||||
mut scan_event_buffer_opt,
|
mut scan_event_buffer_opt,
|
||||||
mut conversation_buffer_opt,
|
|
||||||
follow_target_opt,
|
follow_target_opt,
|
||||||
mut examine_result_buffer_opt,
|
mut examine_result_buffer_opt,
|
||||||
)) = observer_query.single_mut()
|
)) = observer_query.single_mut()
|
||||||
@@ -225,12 +222,6 @@ pub fn compute_observer_snapshot(
|
|||||||
.map(|buf| buf.take())
|
.map(|buf| buf.take())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Drain NPC-to-NPC conversation events (#247, D-078)
|
|
||||||
let (conversation_events, conversation_ended) = conversation_buffer_opt
|
|
||||||
.as_mut()
|
|
||||||
.map(|buf| (buf.take_events(), buf.take_ended()))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
// Collect sound events audible to the observer (D-038, #124).
|
// Collect sound events audible to the observer (D-038, #124).
|
||||||
// Filter by D-018 range: only events the player can hear based on distance.
|
// Filter by D-018 range: only events the player can hear based on distance.
|
||||||
let sound_events = if let Some(ref queue) = sound_queue {
|
let sound_events = if let Some(ref queue) = sound_queue {
|
||||||
@@ -476,8 +467,6 @@ pub fn compute_observer_snapshot(
|
|||||||
dialogue_response,
|
dialogue_response,
|
||||||
blocked_entities,
|
blocked_entities,
|
||||||
scan_events,
|
scan_events,
|
||||||
conversation_events,
|
|
||||||
conversation_ended,
|
|
||||||
follow_state,
|
follow_state,
|
||||||
character_pressure: pressure_query
|
character_pressure: pressure_query
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,6 @@ use crate::knowledge::types::{FactId, KnowledgeConfidence, KnowledgeSource, Stab
|
|||||||
use crate::knowledge::{EntityRegistry, KnowledgeGraph};
|
use crate::knowledge::{EntityRegistry, KnowledgeGraph};
|
||||||
use crate::npc::interaction::{InteractionEvent, InteractionEventKind, InteractionMemory};
|
use crate::npc::interaction::{InteractionEvent, InteractionEventKind, InteractionMemory};
|
||||||
use crate::npc::relationships::{TrustEvent, TrustEventQueue};
|
use crate::npc::relationships::{TrustEvent, TrustEventQueue};
|
||||||
use crate::simulation::conversation::{display_label_for_role, NpcColorIndex, NpcName};
|
|
||||||
use crate::simulation::knowledge_grant::KnowledgeGrant;
|
use crate::simulation::knowledge_grant::KnowledgeGrant;
|
||||||
use crate::simulation::line_pool::LinePoolIndexResource;
|
use crate::simulation::line_pool::LinePoolIndexResource;
|
||||||
use crate::simulation::line_pool::{
|
use crate::simulation::line_pool::{
|
||||||
@@ -35,6 +34,7 @@ use crate::simulation::line_pool::{
|
|||||||
};
|
};
|
||||||
use crate::simulation::monologue::{MonologueBuffer, MonologueState};
|
use crate::simulation::monologue::{MonologueBuffer, MonologueState};
|
||||||
use crate::simulation::movement::PlayerCharacter;
|
use crate::simulation::movement::PlayerCharacter;
|
||||||
|
use crate::simulation::npc_components::{display_label_for_role, NpcColorIndex, NpcName};
|
||||||
use crate::simulation::rng::SimRng;
|
use crate::simulation::rng::SimRng;
|
||||||
use crate::simulation::time::SimulationTime;
|
use crate::simulation::time::SimulationTime;
|
||||||
use crate::storyteller::EngagementRecord;
|
use crate::storyteller::EngagementRecord;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
// PlayerInput: semantic actions (MoveNorth, Interact, UsePerceptionMode, ToggleStance)
|
// PlayerInput: semantic actions (MoveNorth, Interact, UsePerceptionMode, ToggleStance)
|
||||||
|
|
||||||
use crate::bookmark::{BookmarkRegistry, SelectedBookmark};
|
use crate::bookmark::{BookmarkRegistry, SelectedBookmark};
|
||||||
use crate::knowledge::CultureResolverResource;
|
|
||||||
use crate::bridge::debug::DebugCommandBuffer;
|
use crate::bridge::debug::DebugCommandBuffer;
|
||||||
use crate::bridge::types::{
|
use crate::bridge::types::{
|
||||||
FacingDirection, ObjectType, PlayerAction, PlayerInput, SimError, SimErrorKind, SnapshotBuffer,
|
FacingDirection, ObjectType, PlayerAction, PlayerInput, SimError, SimErrorKind, SnapshotBuffer,
|
||||||
};
|
};
|
||||||
|
use crate::knowledge::CultureResolverResource;
|
||||||
use crate::knowledge::{EntityRegistry, StableId};
|
use crate::knowledge::{EntityRegistry, StableId};
|
||||||
use crate::perception::vision_cone::{facing_from_delta, Facing};
|
use crate::perception::vision_cone::{facing_from_delta, Facing};
|
||||||
use crate::settings::{SettingsCommand, SettingsCommandBuffer};
|
use crate::settings::{SettingsCommand, SettingsCommandBuffer};
|
||||||
@@ -449,7 +449,9 @@ pub fn process_player_input(
|
|||||||
buf.pending_bookmark_catalog = Some(registry.build_catalog(resolver));
|
buf.pending_bookmark_catalog = Some(registry.build_catalog(resolver));
|
||||||
tracing::debug!("RequestBookmarkCatalog: catalog staged");
|
tracing::debug!("RequestBookmarkCatalog: catalog staged");
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("RequestBookmarkCatalog: BookmarkRegistry or SnapshotBuffer not available");
|
tracing::warn!(
|
||||||
|
"RequestBookmarkCatalog: BookmarkRegistry or SnapshotBuffer not available"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PlayerAction::ConfirmBookmark {
|
PlayerAction::ConfirmBookmark {
|
||||||
@@ -1208,7 +1210,11 @@ fn handle_confirm_bookmark(
|
|||||||
if let Some(ref mut sel) = selected_bookmark {
|
if let Some(ref mut sel) = selected_bookmark {
|
||||||
sel.bookmark_id = Some(bookmark_id.clone());
|
sel.bookmark_id = Some(bookmark_id.clone());
|
||||||
sel.starting_location_id = Some(starting_location_id.clone());
|
sel.starting_location_id = Some(starting_location_id.clone());
|
||||||
tracing::info!(bookmark_id, starting_location_id, "ConfirmBookmark: selection recorded");
|
tracing::info!(
|
||||||
|
bookmark_id,
|
||||||
|
starting_location_id,
|
||||||
|
"ConfirmBookmark: selection recorded"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use bevy_ecs::schedule::IntoScheduleConfigs;
|
|||||||
pub mod chunk_streaming;
|
pub mod chunk_streaming;
|
||||||
// Phase sub-plugins (#843)
|
// Phase sub-plugins (#843)
|
||||||
pub mod contraband;
|
pub mod contraband;
|
||||||
pub mod conversation;
|
|
||||||
pub mod dialogue;
|
pub mod dialogue;
|
||||||
pub mod economy;
|
pub mod economy;
|
||||||
pub mod economy_plugin;
|
pub mod economy_plugin;
|
||||||
@@ -25,6 +24,7 @@ pub mod modification;
|
|||||||
pub mod monologue;
|
pub mod monologue;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
pub mod movement_plugin;
|
pub mod movement_plugin;
|
||||||
|
pub mod npc_components;
|
||||||
pub mod npc_knowledge_transfer;
|
pub mod npc_knowledge_transfer;
|
||||||
pub mod path_follow;
|
pub mod path_follow;
|
||||||
pub mod pathfinding;
|
pub mod pathfinding;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use rand::Rng;
|
|||||||
use crate::bridge::types::MonologueEvent;
|
use crate::bridge::types::MonologueEvent;
|
||||||
use crate::knowledge::{ContradictionDetectedQueue, EntityRegistry};
|
use crate::knowledge::{ContradictionDetectedQueue, EntityRegistry};
|
||||||
use crate::perception::interpretation::ObservationTrigger;
|
use crate::perception::interpretation::ObservationTrigger;
|
||||||
use crate::simulation::conversation::NpcName;
|
|
||||||
use crate::simulation::movement::{PlayerCharacter, TilePosition};
|
use crate::simulation::movement::{PlayerCharacter, TilePosition};
|
||||||
|
use crate::simulation::npc_components::NpcName;
|
||||||
use crate::simulation::rng::EntityRng;
|
use crate::simulation::rng::EntityRng;
|
||||||
use crate::simulation::time::SimulationTime;
|
use crate::simulation::time::SimulationTime;
|
||||||
use crate::storyteller::EngagementRecord;
|
use crate::storyteller::EngagementRecord;
|
||||||
@@ -84,15 +84,6 @@ const HEAR_SOUND_LINES: &[(&str, &str)] = &[
|
|||||||
("hear_sound_03", "Something just happened nearby."),
|
("hear_sound_03", "Something just happened nearby."),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Hardcoded v0.1 witness_interaction monologue lines.
|
|
||||||
/// Fire when the player overhears an NPC-to-NPC conversation (D-078).
|
|
||||||
/// Future: move to content pools with trigger="witness_interaction".
|
|
||||||
const WITNESS_INTERACTION_LINES: &[(&str, &str)] = &[
|
|
||||||
("witness_01", "Interesting. Wonder what that was about."),
|
|
||||||
("witness_02", "I should remember what they just said."),
|
|
||||||
("witness_03", "They didn't know I was listening."),
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Hardcoded v0.1 post_conversation monologue lines.
|
/// Hardcoded v0.1 post_conversation monologue lines.
|
||||||
/// Fire after a player-NPC dialogue concludes (walk-away or natural end).
|
/// Fire after a player-NPC dialogue concludes (walk-away or natural end).
|
||||||
/// Future: move to content pools with trigger="post_conversation".
|
/// Future: move to content pools with trigger="post_conversation".
|
||||||
@@ -375,7 +366,6 @@ fn select_hardcoded_fallback(trigger: &str, rng: &mut impl Rng) -> (String, Stri
|
|||||||
let lines = match trigger {
|
let lines = match trigger {
|
||||||
"observe_npc" => OBSERVE_NPC_LINES,
|
"observe_npc" => OBSERVE_NPC_LINES,
|
||||||
"hear_sound" => HEAR_SOUND_LINES,
|
"hear_sound" => HEAR_SOUND_LINES,
|
||||||
"witness_interaction" => WITNESS_INTERACTION_LINES,
|
|
||||||
"post_conversation" => POST_CONVERSATION_LINES,
|
"post_conversation" => POST_CONVERSATION_LINES,
|
||||||
unknown => {
|
unknown => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@@ -413,8 +403,7 @@ fn sound_range_tiles(range: &crate::knowledge::types::SoundRange) -> u32 {
|
|||||||
/// Priority order (first match wins):
|
/// Priority order (first match wins):
|
||||||
/// 1. observe_npc (new entity spotted — uses previous-tick observation events)
|
/// 1. observe_npc (new entity spotted — uses previous-tick observation events)
|
||||||
/// 2. hear_sound (non-routine sound: Machinery, Alert)
|
/// 2. hear_sound (non-routine sound: Machinery, Alert)
|
||||||
/// 3. witness_interaction (overheard NPC-to-NPC conversation, D-078)
|
/// 3. post_conversation (player-NPC dialogue concluded)
|
||||||
/// 4. post_conversation (player-NPC dialogue concluded)
|
|
||||||
///
|
///
|
||||||
/// System ordering: after all event producers + recognition/anomaly monologue
|
/// System ordering: after all event producers + recognition/anomaly monologue
|
||||||
/// systems, before compute_observer_snapshot.
|
/// systems, before compute_observer_snapshot.
|
||||||
@@ -429,7 +418,6 @@ pub fn trigger_event_monologue(
|
|||||||
&TilePosition,
|
&TilePosition,
|
||||||
&mut MonologueState,
|
&mut MonologueState,
|
||||||
&mut MonologueBuffer,
|
&mut MonologueBuffer,
|
||||||
Option<&crate::simulation::conversation::ConversationEventBuffer>,
|
|
||||||
&mut EntityRng,
|
&mut EntityRng,
|
||||||
),
|
),
|
||||||
With<PlayerCharacter>,
|
With<PlayerCharacter>,
|
||||||
@@ -441,9 +429,7 @@ pub fn trigger_event_monologue(
|
|||||||
// Saved for NPC attribution (engagement tracking #570) and trigger detection.
|
// Saved for NPC attribution (engagement tracking #570) and trigger detection.
|
||||||
let post_conv_npcs: Vec<Entity> = post_conv_queue.drain();
|
let post_conv_npcs: Vec<Entity> = post_conv_queue.drain();
|
||||||
|
|
||||||
let Ok((player_pos, mut state, mut buffer, conv_buffer_opt, mut entity_rng)) =
|
let Ok((player_pos, mut state, mut buffer, mut entity_rng)) = query.single_mut() else {
|
||||||
query.single_mut()
|
|
||||||
else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -468,11 +454,6 @@ pub fn trigger_event_monologue(
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
Some("hear_sound")
|
Some("hear_sound")
|
||||||
} else if conv_buffer_opt
|
|
||||||
.map(|b| !b.events.is_empty())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
Some("witness_interaction")
|
|
||||||
} else if !post_conv_npcs.is_empty() {
|
} else if !post_conv_npcs.is_empty() {
|
||||||
Some("post_conversation")
|
Some("post_conversation")
|
||||||
} else {
|
} else {
|
||||||
@@ -511,7 +492,7 @@ pub fn trigger_event_monologue(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Engagement tracking (#570): attribute monologue_trigger_count to specific NPCs.
|
// Engagement tracking (#570): attribute monologue_trigger_count to specific NPCs.
|
||||||
// Only NPC-context triggers are attributed — hear_sound/witness_interaction are not NPC-specific.
|
// Only NPC-context triggers are attributed — hear_sound is not NPC-specific.
|
||||||
match trigger {
|
match trigger {
|
||||||
"observe_npc" => {
|
"observe_npc" => {
|
||||||
// Attribute to all NPCs whose NewEntity event triggered this monologue
|
// Attribute to all NPCs whose NewEntity event triggered this monologue
|
||||||
@@ -537,7 +518,7 @@ pub fn trigger_event_monologue(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {} // hear_sound, witness_interaction: no NPC-specific attribution
|
_ => {} // hear_sound: no NPC-specific attribution
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1345,7 +1326,6 @@ mod tests {
|
|||||||
use crate::perception::interpretation::{
|
use crate::perception::interpretation::{
|
||||||
ObservationEvent, ObservationEventQueue, ObservationTrigger,
|
ObservationEvent, ObservationEventQueue, ObservationTrigger,
|
||||||
};
|
};
|
||||||
use crate::simulation::conversation::ConversationEventBuffer;
|
|
||||||
use crate::simulation::sound::{SoundEvent, SoundEventKind, SoundEventQueue};
|
use crate::simulation::sound::{SoundEvent, SoundEventKind, SoundEventQueue};
|
||||||
|
|
||||||
fn setup_event_world() -> World {
|
fn setup_event_world() -> World {
|
||||||
@@ -1366,7 +1346,6 @@ mod tests {
|
|||||||
TilePosition::new(10, 10, 0),
|
TilePosition::new(10, 10, 0),
|
||||||
MonologueState::default(),
|
MonologueState::default(),
|
||||||
MonologueBuffer::default(),
|
MonologueBuffer::default(),
|
||||||
ConversationEventBuffer::default(),
|
|
||||||
))
|
))
|
||||||
.id()
|
.id()
|
||||||
}
|
}
|
||||||
@@ -1521,36 +1500,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn witness_interaction_fires_on_conversation_event() {
|
|
||||||
let mut world = setup_event_world();
|
|
||||||
let player = spawn_event_player(&mut world);
|
|
||||||
|
|
||||||
// Pre-fill ConversationEventBuffer with an overheard conversation
|
|
||||||
world
|
|
||||||
.get_mut::<ConversationEventBuffer>(player)
|
|
||||||
.unwrap()
|
|
||||||
.events
|
|
||||||
.push(crate::simulation::conversation::ConversationEvent {
|
|
||||||
occluded_line: "Keep your head down today.".to_string(),
|
|
||||||
speaker_id: 100,
|
|
||||||
target_id: 101,
|
|
||||||
speaker_name: "Worker".to_string(),
|
|
||||||
target_name: "Courier".to_string(),
|
|
||||||
speaker_color_index: 0,
|
|
||||||
target_color_index: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
run_event_system(&mut world);
|
|
||||||
|
|
||||||
let buf = world.get::<MonologueBuffer>(player).unwrap();
|
|
||||||
assert!(
|
|
||||||
buf.event.is_some(),
|
|
||||||
"witness_interaction should fire when conversation overheard"
|
|
||||||
);
|
|
||||||
assert!(buf.event.as_ref().unwrap().id.starts_with("witness_"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_conversation_fires_on_queue_entry() {
|
fn post_conversation_fires_on_queue_entry() {
|
||||||
let mut world = setup_event_world();
|
let mut world = setup_event_world();
|
||||||
@@ -1668,47 +1617,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn priority_hear_sound_over_witness_interaction() {
|
|
||||||
let mut world = setup_event_world();
|
|
||||||
let player = spawn_event_player(&mut world);
|
|
||||||
|
|
||||||
// Sound event
|
|
||||||
world
|
|
||||||
.resource_mut::<SoundEventQueue>()
|
|
||||||
.events
|
|
||||||
.push(SoundEvent::at(
|
|
||||||
&TilePosition::new(11, 10, 0),
|
|
||||||
SoundEventKind::Alert,
|
|
||||||
1.0,
|
|
||||||
crate::knowledge::types::SoundRange::Medium,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Conversation event
|
|
||||||
world
|
|
||||||
.get_mut::<ConversationEventBuffer>(player)
|
|
||||||
.unwrap()
|
|
||||||
.events
|
|
||||||
.push(crate::simulation::conversation::ConversationEvent {
|
|
||||||
occluded_line: "Test".to_string(),
|
|
||||||
speaker_id: 100,
|
|
||||||
target_id: 101,
|
|
||||||
speaker_name: "A".to_string(),
|
|
||||||
target_name: "B".to_string(),
|
|
||||||
speaker_color_index: 0,
|
|
||||||
target_color_index: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
run_event_system(&mut world);
|
|
||||||
|
|
||||||
let buf = world.get::<MonologueBuffer>(player).unwrap();
|
|
||||||
assert!(
|
|
||||||
buf.event.as_ref().unwrap().id.starts_with("hear_sound_"),
|
|
||||||
"hear_sound should have priority over witness_interaction"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn event_trigger_updates_last_fired_tick() {
|
fn event_trigger_updates_last_fired_tick() {
|
||||||
let mut world = setup_event_world();
|
let mut world = setup_event_world();
|
||||||
@@ -1813,12 +1721,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hardcoded_lines_all_valid() {
|
fn hardcoded_lines_all_valid() {
|
||||||
for lines in &[
|
for lines in &[OBSERVE_NPC_LINES, HEAR_SOUND_LINES, POST_CONVERSATION_LINES] {
|
||||||
OBSERVE_NPC_LINES,
|
|
||||||
HEAR_SOUND_LINES,
|
|
||||||
WITNESS_INTERACTION_LINES,
|
|
||||||
POST_CONVERSATION_LINES,
|
|
||||||
] {
|
|
||||||
assert!(!lines.is_empty());
|
assert!(!lines.is_empty());
|
||||||
for (id, text) in *lines {
|
for (id, text) in *lines {
|
||||||
assert!(!id.is_empty(), "line id should not be empty");
|
assert!(!id.is_empty(), "line id should not be empty");
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// NPC component types shared across simulation systems.
|
||||||
|
//
|
||||||
|
// Extracted from conversation.rs (D-078 scrapped per R-012). These types
|
||||||
|
// survive because they are used by dialogue, debug, knowledge transfer,
|
||||||
|
// monologue, and the D-080 knowledge propagation system.
|
||||||
|
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Display name for an NPC.
|
||||||
|
/// Attached during content spawn.
|
||||||
|
#[derive(Component, Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct NpcName(pub String);
|
||||||
|
|
||||||
|
/// Map a dialogue role string to a display label for use when the player
|
||||||
|
/// does not yet know the NPC's real name.
|
||||||
|
pub fn display_label_for_role(role: &str) -> String {
|
||||||
|
match role {
|
||||||
|
"dock-worker" => "Dock Worker",
|
||||||
|
"courier" => "Courier",
|
||||||
|
"maintenance-tech" => "Technician",
|
||||||
|
"new-hire" | "day-worker" | "transit-worker" => "Worker",
|
||||||
|
"scheduler" => "Scheduler",
|
||||||
|
"shift-supervisor" => "Supervisor",
|
||||||
|
"bartender" => "Bartender",
|
||||||
|
"bar-regular" => "Patron",
|
||||||
|
_ => "Bystander",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Color index (0-7) for rendering this NPC with a distinct color.
|
||||||
|
/// Assigned at spawn time as `(stable_id % 8)`.
|
||||||
|
#[derive(Component, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct NpcColorIndex(pub u8);
|
||||||
|
|
||||||
|
/// Active NPC-to-NPC conversation session (D-080 knowledge propagation).
|
||||||
|
/// Attached to the "speaker" NPC (the one who initiated).
|
||||||
|
/// The "listener" is tracked by entity reference.
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct NpcConversation {
|
||||||
|
/// The other NPC in the conversation.
|
||||||
|
pub partner: Entity,
|
||||||
|
/// Tick when the conversation started.
|
||||||
|
pub started_tick: u64,
|
||||||
|
/// Tick when the conversation will end.
|
||||||
|
pub end_tick: u64,
|
||||||
|
/// Ticks since last line was spoken (for pacing).
|
||||||
|
pub ticks_since_last_line: u64,
|
||||||
|
}
|
||||||
@@ -28,8 +28,8 @@ use crate::knowledge::{
|
|||||||
};
|
};
|
||||||
use crate::npc::relationships::RelationshipGraph;
|
use crate::npc::relationships::RelationshipGraph;
|
||||||
use crate::npc::Npc;
|
use crate::npc::Npc;
|
||||||
use crate::simulation::conversation::NpcConversation;
|
|
||||||
use crate::simulation::movement::{PlayerCharacter, TilePosition};
|
use crate::simulation::movement::{PlayerCharacter, TilePosition};
|
||||||
|
use crate::simulation::npc_components::NpcConversation;
|
||||||
use crate::simulation::rng::SimRng;
|
use crate::simulation::rng::SimRng;
|
||||||
use crate::simulation::tier::ActiveSim;
|
use crate::simulation::tier::ActiveSim;
|
||||||
use crate::simulation::time::SimulationTime;
|
use crate::simulation::time::SimulationTime;
|
||||||
@@ -438,8 +438,8 @@ mod tests {
|
|||||||
use crate::knowledge::{EntityRegistry, KnowledgeGraph};
|
use crate::knowledge::{EntityRegistry, KnowledgeGraph};
|
||||||
use crate::npc::relationships::{RelationshipEdge, RelationshipGraph};
|
use crate::npc::relationships::{RelationshipEdge, RelationshipGraph};
|
||||||
use crate::npc::RelationshipKind;
|
use crate::npc::RelationshipKind;
|
||||||
use crate::simulation::conversation::NpcConversation;
|
|
||||||
use crate::simulation::movement::TilePosition;
|
use crate::simulation::movement::TilePosition;
|
||||||
|
use crate::simulation::npc_components::NpcConversation;
|
||||||
use crate::simulation::rng::SimRng;
|
use crate::simulation::rng::SimRng;
|
||||||
use crate::simulation::tier::ActiveSim;
|
use crate::simulation::tier::ActiveSim;
|
||||||
use crate::simulation::time::SimulationTime;
|
use crate::simulation::time::SimulationTime;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
//! Social simulation plugin — NPC conversations, knowledge transfer, disclosure.
|
//! Social simulation plugin — NPC knowledge transfer, disclosure, and social systems.
|
||||||
//!
|
//!
|
||||||
//! All systems run in [`TickPhase::Simulation`]. Intra-phase ordering:
|
//! All systems run in [`TickPhase::Simulation`].
|
||||||
//! - conversations → knowledge_transfer (transfer reads conversation results)
|
|
||||||
//! - conversations → sound collection (sound reads conversation events)
|
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_ecs::schedule::IntoScheduleConfigs;
|
use bevy_ecs::schedule::IntoScheduleConfigs;
|
||||||
@@ -20,16 +18,11 @@ impl Plugin for SocialPlugin {
|
|||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
super::conversation::run_npc_conversations,
|
super::npc_knowledge_transfer::transfer_npc_knowledge,
|
||||||
super::npc_knowledge_transfer::transfer_npc_knowledge
|
super::sound::collect_sound_events,
|
||||||
.after(super::conversation::run_npc_conversations),
|
|
||||||
super::sound::collect_sound_events
|
|
||||||
.after(super::conversation::run_npc_conversations),
|
|
||||||
// Voice enrichment (D-138) — rewrite NPC text with voiced variants.
|
// Voice enrichment (D-138) — rewrite NPC text with voiced variants.
|
||||||
// No-op when VoiceCacheResource is absent.
|
// No-op when VoiceCacheResource is absent.
|
||||||
crate::voice::integration::voice_enrich_dialogue_response,
|
crate::voice::integration::voice_enrich_dialogue_response,
|
||||||
crate::voice::integration::voice_enrich_conversation_events
|
|
||||||
.after(super::conversation::run_npc_conversations),
|
|
||||||
// POI discovery reads visibility geometry (also Simulation phase)
|
// POI discovery reads visibility geometry (also Simulation phase)
|
||||||
super::poi_discovery::discover_pois,
|
super::poi_discovery::discover_pois,
|
||||||
// Follow state reads visibility geometry + movement
|
// Follow state reads visibility geometry + movement
|
||||||
|
|||||||
@@ -614,8 +614,8 @@ pub fn setup_gauntlet(app: &mut App, archetype: crate::bridge::types::CharacterA
|
|||||||
// can respond to Talk using content from the YAML dialogue pools.
|
// can respond to Talk using content from the YAML dialogue pools.
|
||||||
{
|
{
|
||||||
use crate::npc::Npc;
|
use crate::npc::Npc;
|
||||||
use crate::simulation::conversation::NpcColorIndex;
|
|
||||||
use crate::simulation::dialogue::{CurrentMood, DialogueProfile};
|
use crate::simulation::dialogue::{CurrentMood, DialogueProfile};
|
||||||
|
use crate::simulation::npc_components::NpcColorIndex;
|
||||||
|
|
||||||
// (location, role) pairs matching server/content/campaigns/.../dialogue/ YAML pools.
|
// (location, role) pairs matching server/content/campaigns/.../dialogue/ YAML pools.
|
||||||
// Cycling through these gives NPC variety across rooms.
|
// Cycling through these gives NPC variety across rooms.
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
//! Observer integration for the voice pipeline (D-138, Phase 3).
|
//! Observer integration for the voice pipeline (D-138, Phase 3).
|
||||||
//!
|
//!
|
||||||
//! Two enrichment systems run before `compute_observer_snapshot` and rewrite
|
//! One enrichment system runs before `compute_observer_snapshot` and rewrites
|
||||||
//! NPC text in the dialogue and conversation buffers with voiced variants
|
//! NPC dialogue text with voiced variants looked up from the cache.
|
||||||
//! looked up from the cache. Cache miss → base text (never blocks).
|
//! Cache miss → base text (never blocks).
|
||||||
//!
|
//!
|
||||||
//! ## System ordering
|
//! ## System ordering
|
||||||
//!
|
//!
|
||||||
//! ```text
|
//! ```text
|
||||||
//! process_talk_interaction ─┐
|
//! process_talk_interaction ─► voice_enrich_dialogue_response ─► compute_observer_snapshot
|
||||||
//! run_npc_conversations ─┤─► voice_enrich_dialogue_response ─┐
|
|
||||||
//! └─► voice_enrich_conversation_events ─► compute_observer_snapshot
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Content index derivation
|
//! ## Content index derivation
|
||||||
@@ -24,7 +22,6 @@ use bevy_ecs::prelude::*;
|
|||||||
use crate::knowledge::{EntityRegistry, StableId};
|
use crate::knowledge::{EntityRegistry, StableId};
|
||||||
use crate::npc::tell_state::DerivedTellState;
|
use crate::npc::tell_state::DerivedTellState;
|
||||||
use crate::npc::{Npc, NpcVoiceProfile};
|
use crate::npc::{Npc, NpcVoiceProfile};
|
||||||
use crate::simulation::conversation::ConversationEventBuffer;
|
|
||||||
use crate::simulation::dialogue::DialogueResponseBuffer;
|
use crate::simulation::dialogue::DialogueResponseBuffer;
|
||||||
use crate::simulation::movement::{PlayerCharacter, TilePosition};
|
use crate::simulation::movement::{PlayerCharacter, TilePosition};
|
||||||
use crate::simulation::zone::ZoneMap;
|
use crate::simulation::zone::ZoneMap;
|
||||||
@@ -127,85 +124,6 @@ pub fn voice_enrich_dialogue_response(
|
|||||||
response.text = voiced;
|
response.text = voiced;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enrich the conversation event buffer with voiced lines from the cache.
|
|
||||||
///
|
|
||||||
/// Must run after `run_npc_conversations` and before
|
|
||||||
/// `compute_observer_snapshot`. No-op when the voice cache resource is absent.
|
|
||||||
///
|
|
||||||
/// Each event in the buffer is processed: the pre-occlusion base text is
|
|
||||||
/// not available at this point (occlusion has already been applied), so the
|
|
||||||
/// `occluded_line` is treated as the base text for the voice lookup.
|
|
||||||
/// This means the voice register wraps the already-occluded line.
|
|
||||||
///
|
|
||||||
/// ## Accepted risk: re-voicing of heavily occluded text
|
|
||||||
///
|
|
||||||
/// When many words are dropped by D-078 occlusion, the remaining text may
|
|
||||||
/// be fragmentary ("... the ... came in ..."). Re-voicing such fragments can
|
|
||||||
/// produce incoherent output. This is acceptable for two reasons:
|
|
||||||
/// 1. Cache misses are common for conversation text (no pre-baking pipeline),
|
|
||||||
/// so the base text fallback in `voiced_behavior()` fires most of the time.
|
|
||||||
/// 2. Even incoherent voiced output is no worse than the already-degraded
|
|
||||||
/// overheard line — the occlusion itself has already broken coherence.
|
|
||||||
/// The player's inability to fully parse overheard speech is the mechanic.
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn voice_enrich_conversation_events(
|
|
||||||
voice_cache: Option<Res<VoiceCacheResource>>,
|
|
||||||
registry: Res<EntityRegistry>,
|
|
||||||
zone_map: Option<Res<ZoneMap>>,
|
|
||||||
player_query: Query<&TilePosition, With<PlayerCharacter>>,
|
|
||||||
npc_voice_query: Query<(&NpcVoiceProfile, Option<&DerivedTellState>), With<Npc>>,
|
|
||||||
mut conversation_buffer: Query<&mut ConversationEventBuffer, With<PlayerCharacter>>,
|
|
||||||
) {
|
|
||||||
let Some(voice_cache) = voice_cache else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(mut buffer) = conversation_buffer.single_mut() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if buffer.events.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let zone_id = player_zone_id(&player_query, zone_map.as_deref());
|
|
||||||
|
|
||||||
for event in &mut buffer.events {
|
|
||||||
let speaker_entity = registry.to_entity(&StableId(event.speaker_id));
|
|
||||||
let Some(entity) = speaker_entity else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok((voice_profile, tell_state_opt)) = npc_voice_query.get(entity) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let tell_state = tell_state_opt.and_then(|t| t.category);
|
|
||||||
// Conversation lines don't have a stable line_id; derive content_index
|
|
||||||
// from the occluded line text. The full cache key is
|
|
||||||
// (culture_id, npc_stable_id, content_type, content_index, tell_state),
|
|
||||||
// so a u16 hash collision requires two different lines from the same
|
|
||||||
// speaker with the same tell state to hash identically — at ~65K
|
|
||||||
// possible values this is extremely rare, and the worst outcome is
|
|
||||||
// a stale voiced line being served instead of base text. Acceptable.
|
|
||||||
let content_index = content_index_from_line_id(&event.occluded_line);
|
|
||||||
|
|
||||||
let voiced = voiced_behavior(
|
|
||||||
&voice_cache.cache,
|
|
||||||
zone_id,
|
|
||||||
&voice_profile.culture_id,
|
|
||||||
event.speaker_id,
|
|
||||||
ContentType::Dialogue,
|
|
||||||
content_index,
|
|
||||||
tell_state,
|
|
||||||
&event.occluded_line,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
event.occluded_line = voiced;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Tests
|
// Tests
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ fn snapshot_roundtrip_over_unix_socket() {
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ fn snapshot_roundtrip_over_tcp() {
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
|
|||||||
@@ -288,8 +288,7 @@ fn snapshot_with_sim_errors_roundtrips() {
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ fn fixture_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
@@ -238,8 +237,7 @@ fn generate_msgpack_fixtures() {
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
@@ -379,8 +377,7 @@ fn generate_msgpack_fixtures() {
|
|||||||
blocked_entities: vec![5, 6],
|
blocked_entities: vec![5, 6],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: Some(0xDEADBEEF),
|
rng_seed: Some(0xDEADBEEF),
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
3
|
3
|
||||||
],
|
],
|
||||||
"character_pressure": null,
|
"character_pressure": null,
|
||||||
"conversation_ended": [],
|
|
||||||
"conversation_events": [],
|
|
||||||
"current_monologue": null,
|
"current_monologue": null,
|
||||||
"dialogue_response": null,
|
"dialogue_response": null,
|
||||||
"entities": [
|
"entities": [
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ fn test_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot {
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
@@ -293,8 +292,7 @@ fn snapshot_v2_fields_roundtrip() {
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
@@ -405,8 +403,7 @@ fn all_facing_direction_variants_roundtrip() {
|
|||||||
blocked_entities: vec![],
|
blocked_entities: vec![],
|
||||||
scan_events: vec![],
|
scan_events: vec![],
|
||||||
sound_events: vec![],
|
sound_events: vec![],
|
||||||
conversation_events: vec![],
|
|
||||||
conversation_ended: vec![],
|
|
||||||
follow_state: None,
|
follow_state: None,
|
||||||
character_pressure: None,
|
character_pressure: None,
|
||||||
rng_seed: None,
|
rng_seed: None,
|
||||||
@@ -1456,8 +1453,7 @@ fn serde_default_fields_fill_in_when_missing_from_wire() {
|
|||||||
"blocked_entities": [],
|
"blocked_entities": [],
|
||||||
"scan_events": [],
|
"scan_events": [],
|
||||||
"sound_events": [],
|
"sound_events": [],
|
||||||
"conversation_events": [],
|
|
||||||
"conversation_ended": [],
|
|
||||||
"follow_state": null,
|
"follow_state": null,
|
||||||
"rng_seed": null
|
"rng_seed": null
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user