Production startup now spawns 23 Sova NPCs with EntanglementTag (Flat/Intrigue) based on triangle_membership. Three-phase spawn: entity creation, cross-reference resolution, and authored triangle instantiation. Five triangles (3 ActiveFork, 2 PassiveTension per D-087) with deterministic IDs via FNV-1a hashing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+3
-2
@@ -1,7 +1,8 @@
|
||||
# Triangle 2: Worried Knowledge — unreported evidence and protective silence
|
||||
# Active fork — Sera's choice about what to do with what she knows
|
||||
# Triangle 3: Worried Knowledge — unreported evidence and protective silence
|
||||
# Passive tension (D-087) — background pressure, not a direct player decision point
|
||||
canonical_id: worried-knowledge
|
||||
display_name: "Worried Knowledge"
|
||||
classification: passive_tension
|
||||
description: >
|
||||
Sera Venn (Commission field tech, detective's FRIEND) is sitting on
|
||||
unreported evidence about Kael's manifest discrepancies. She's protecting
|
||||
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
# Triangle 4: Worried Partner — ring pressure on personal relationships
|
||||
# Active fork — the emotional heart of the smuggler's FRIEND arc
|
||||
# Triangle 5: Worried Partner — ring pressure on personal relationships
|
||||
# Passive tension (D-087) — background pressure, not a direct player decision point
|
||||
canonical_id: worried-partner
|
||||
display_name: "Worried Partner"
|
||||
classification: passive_tension
|
||||
description: >
|
||||
Kael Davan (dock worker, ring member, smuggler's FRIEND) is under
|
||||
increasing ring pressure from Devra (ring coordinator). Naia Tamm
|
||||
|
||||
+114
-4
@@ -1,4 +1,4 @@
|
||||
//! Content → ECS entity spawning (two-phase).
|
||||
//! Content → ECS entity spawning (three-phase).
|
||||
//!
|
||||
//! Maps intermediate content types from the loader into bevy_ecs
|
||||
//! Components and Resources. The separation ensures content schema
|
||||
@@ -38,6 +38,7 @@ use crate::simulation::time::DayPhase;
|
||||
use rand::Rng as _;
|
||||
use crate::content::template::{
|
||||
FullTemplateDef, RoleId, TemplateId, TemplateOwnership, TemplateReference, TemplateReferenceMap,
|
||||
TriangleClassification, TriangleId, TrianglePhase, TriangleState,
|
||||
};
|
||||
use crate::npc::generate::{generate_npc, RoleDefinition};
|
||||
use crate::npc::{Relationship, Relationships};
|
||||
@@ -68,7 +69,8 @@ pub struct SpawnResult {
|
||||
/// Spawn all loaded content into the ECS world.
|
||||
///
|
||||
/// This is the main entry point for content → ECS conversion.
|
||||
/// Runs Phase 1 (entity spawning) then Phase 2 (cross-reference resolution).
|
||||
/// Runs Phase 1 (entity spawning), Phase 2 (cross-reference resolution),
|
||||
/// then Phase 3 (authored triangle instantiation, #188).
|
||||
pub fn spawn_content(world: &mut World, store: &ContentStore) -> SpawnResult {
|
||||
// Phase 1: spawn entities and build canonical_id → StableId map
|
||||
let result = spawn_entities(world, store);
|
||||
@@ -76,9 +78,13 @@ pub fn spawn_content(world: &mut World, store: &ContentStore) -> SpawnResult {
|
||||
// Phase 2: resolve cross-references using the id map
|
||||
resolve_cross_references(world, store, &result);
|
||||
|
||||
// Phase 3: instantiate authored triangles (#188, D-087)
|
||||
let triangles_spawned = instantiate_authored_triangles(world, store, &result);
|
||||
|
||||
tracing::info!(
|
||||
"Content spawn complete: {} NPCs (phase 1), cross-references resolved (phase 2)",
|
||||
result.npcs_spawned
|
||||
"Content spawn complete: {} NPCs (phase 1), cross-references resolved (phase 2), {} triangles (phase 3)",
|
||||
result.npcs_spawned,
|
||||
triangles_spawned
|
||||
);
|
||||
|
||||
result
|
||||
@@ -214,6 +220,16 @@ fn spawn_npc(world: &mut World, profile: &types::NpcProfile, result: &mut SpawnR
|
||||
// TODO: CombatCapability — no content schema type exists yet. When combat content
|
||||
// is authored, add weapon_proficiency + combat_style mapping here.
|
||||
|
||||
// Entanglement tag (D-029, #176) — derived from authored triangle_membership.
|
||||
// Non-empty triangle_membership → Intrigue (active narrative participant).
|
||||
// Empty → Flat (background population). Mundane reserved for procedural NPCs.
|
||||
let entanglement = if profile.triangle_membership.is_empty() {
|
||||
npc::EntanglementTag::Flat
|
||||
} else {
|
||||
npc::EntanglementTag::Intrigue
|
||||
};
|
||||
entity_commands.insert(entanglement);
|
||||
|
||||
// Vision + awareness components (#115, #244) — must match generate_npc().
|
||||
// Without these, vision/awareness systems silently skip content-spawned NPCs.
|
||||
entity_commands.insert((
|
||||
@@ -535,6 +551,100 @@ fn resolve_routines(
|
||||
tracing::debug!("Resolved routines for {} NPCs", routines_resolved);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Phase 3: Authored triangle instantiation (#188, D-087)
|
||||
// ===========================================================================
|
||||
|
||||
/// Phase 3: Instantiate authored triangles from content YAML.
|
||||
///
|
||||
/// Authored triangles (triangles/*.yaml) define fixed narrative structures
|
||||
/// with NPC members referenced by canonical_id. Unlike template triangles
|
||||
/// (generated from TriangleDef role triples), authored triangles carry
|
||||
/// a canonical slug, explicit member assignments, and D-087 classification.
|
||||
///
|
||||
/// Returns the number of triangles successfully spawned.
|
||||
fn instantiate_authored_triangles(
|
||||
world: &mut World,
|
||||
store: &ContentStore,
|
||||
result: &SpawnResult,
|
||||
) -> u32 {
|
||||
let mut count = 0u32;
|
||||
|
||||
// Sentinel template_id for authored (non-template) triangles.
|
||||
let authored_template_id = TemplateId::from_seed_and_slug(0, "authored");
|
||||
|
||||
for (district_id, content) in &store.districts {
|
||||
for triangle in &content.triangles {
|
||||
// Resolve NPC member references to StableIds.
|
||||
// YAML uses "npc:kael-davan" — matches NpcProfile.canonical_id directly.
|
||||
let mut role_assignments = BTreeMap::new();
|
||||
let mut all_resolved = true;
|
||||
|
||||
for member in &triangle.members {
|
||||
let Some(&stable_id) = result.npc_ids.get(&member.npc) else {
|
||||
tracing::warn!(
|
||||
"Triangle '{}' in district '{}': cannot resolve member '{}' — NPC not in npc_ids map",
|
||||
triangle.canonical_id,
|
||||
district_id,
|
||||
member.npc,
|
||||
);
|
||||
all_resolved = false;
|
||||
break;
|
||||
};
|
||||
|
||||
role_assignments.insert(RoleId::new(&member.role), stable_id);
|
||||
}
|
||||
|
||||
if !all_resolved {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine D-087 classification from YAML field.
|
||||
let classification = match triangle.classification.as_deref() {
|
||||
Some("passive_tension") => TriangleClassification::PassiveTension,
|
||||
_ => TriangleClassification::ActiveFork,
|
||||
};
|
||||
|
||||
// Initial phase: ActiveFork starts Simmering (tension building),
|
||||
// PassiveTension starts Dormant (background, awaiting conditions).
|
||||
let phase = match classification {
|
||||
TriangleClassification::ActiveFork => TrianglePhase::Simmering,
|
||||
TriangleClassification::PassiveTension => TrianglePhase::Dormant,
|
||||
};
|
||||
|
||||
// Deterministic TriangleId from canonical_id slug (D-010).
|
||||
let triangle_id = TriangleId::from_seed_and_slug(0, &triangle.canonical_id);
|
||||
|
||||
let state = TriangleState {
|
||||
triangle_id,
|
||||
role_assignments,
|
||||
tension: 0,
|
||||
phase,
|
||||
tension_rate: 1,
|
||||
template_id: authored_template_id,
|
||||
classification,
|
||||
};
|
||||
|
||||
world.spawn((state, ActiveSim));
|
||||
count += 1;
|
||||
|
||||
tracing::debug!(
|
||||
"Spawned authored triangle: {} ({:?}, {:?})",
|
||||
triangle.canonical_id,
|
||||
classification,
|
||||
phase,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Phase 3 complete: {} authored triangles spawned",
|
||||
count
|
||||
);
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Content value → ECS enum mapping functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -332,6 +332,19 @@ impl TemplateReferenceMap {
|
||||
pub struct TriangleId(pub u64);
|
||||
|
||||
impl TriangleId {
|
||||
/// Compute a deterministic `TriangleId` from a seed and a canonical slug.
|
||||
///
|
||||
/// Used for authored triangles loaded from content YAML (#188).
|
||||
/// Mirrors `TemplateId::from_seed_and_slug` — same FNV-1a pattern (D-010).
|
||||
pub fn from_seed_and_slug(seed: u64, slug: &str) -> Self {
|
||||
let mut hash = seed ^ 0xcbf29ce484222325; // FNV-1a offset basis, XOR'd with seed
|
||||
for byte in slug.as_bytes() {
|
||||
hash ^= *byte as u64;
|
||||
hash = hash.wrapping_mul(0x100000001b3); // FNV-1a prime
|
||||
}
|
||||
TriangleId(hash)
|
||||
}
|
||||
|
||||
/// Compute a deterministic `TriangleId` from a seed and three role IDs.
|
||||
///
|
||||
/// Roles are sorted before hashing to ensure the same triple always produces
|
||||
@@ -591,6 +604,20 @@ impl FullTemplateDef {
|
||||
// #107 — Intra-template triangle generation
|
||||
// ===========================================================================
|
||||
|
||||
/// Narrative classification of a triangle (D-087, #188).
|
||||
///
|
||||
/// Active forks drive narrative conflict — the player's decisions directly
|
||||
/// affect outcomes. Passive tensions provide background pressure — observable
|
||||
/// behavioral signals without a direct player decision point.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum TriangleClassification {
|
||||
/// Drives narrative conflict — player decisions affect outcomes (D-087).
|
||||
#[default]
|
||||
ActiveFork,
|
||||
/// Background tension — observable tells without direct decision point.
|
||||
PassiveTension,
|
||||
}
|
||||
|
||||
/// Phase of a triangle's lifecycle.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum TrianglePhase {
|
||||
@@ -624,6 +651,9 @@ pub struct TriangleState {
|
||||
pub tension_rate: u8,
|
||||
/// Which template owns this triangle.
|
||||
pub template_id: TemplateId,
|
||||
/// Narrative classification (D-087, #188): active fork vs passive tension.
|
||||
#[serde(default)]
|
||||
pub classification: TriangleClassification,
|
||||
}
|
||||
|
||||
/// Result of triangle generation for a single template.
|
||||
@@ -745,6 +775,7 @@ pub fn generate_intra_template_triangles(
|
||||
phase: TrianglePhase::Simmering,
|
||||
tension_rate,
|
||||
template_id,
|
||||
classification: TriangleClassification::ActiveFork,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -970,6 +1001,7 @@ pub fn generate_cross_template_triangles(
|
||||
phase: TrianglePhase::Simmering,
|
||||
tension_rate,
|
||||
template_id: template_a_id, // cross-template triangle owned by template_a
|
||||
classification: TriangleClassification::ActiveFork,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1591,6 +1623,7 @@ mod tests {
|
||||
phase: TrianglePhase::Simmering,
|
||||
tension_rate: 3,
|
||||
template_id: TemplateId(100),
|
||||
classification: TriangleClassification::ActiveFork,
|
||||
};
|
||||
|
||||
let bytes = rmp_serde::to_vec_named(&state).expect("serialize");
|
||||
@@ -1632,6 +1665,7 @@ mod tests {
|
||||
phase: TrianglePhase::Active,
|
||||
tension_rate: 5,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::ActiveFork,
|
||||
},
|
||||
ActiveSim,
|
||||
))
|
||||
@@ -1676,6 +1710,7 @@ mod tests {
|
||||
phase: TrianglePhase::Active,
|
||||
tension_rate: 5,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::ActiveFork,
|
||||
},
|
||||
ActiveSim,
|
||||
))
|
||||
@@ -1702,6 +1737,7 @@ mod tests {
|
||||
phase: TrianglePhase::Active,
|
||||
tension_rate: 3,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::ActiveFork,
|
||||
})
|
||||
.id();
|
||||
|
||||
@@ -1713,6 +1749,7 @@ mod tests {
|
||||
phase: TrianglePhase::Simmering,
|
||||
tension_rate: 2,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::ActiveFork,
|
||||
})
|
||||
.id();
|
||||
|
||||
|
||||
@@ -196,6 +196,9 @@ pub struct Triangle {
|
||||
pub forks: Vec<Fork>,
|
||||
#[serde(default)]
|
||||
pub resolution_states: Vec<Resolution>,
|
||||
/// D-087 classification: "active_fork" (default) or "passive_tension".
|
||||
#[serde(default)]
|
||||
pub classification: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
@@ -152,6 +152,25 @@ pub enum DeviationTrigger {
|
||||
Confrontation,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Entanglement tag (D-029, #176)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Marks an NPC's narrative entanglement level (D-029).
|
||||
///
|
||||
/// - `Flat`: background population — no triangle involvement, minimal story role.
|
||||
/// - `Mundane`: has routines and personality but no active triangle membership.
|
||||
/// - `Intrigue`: participates in at least one triangle — drives narrative tension.
|
||||
///
|
||||
/// For authored NPCs: determined by YAML `triangle_membership` field.
|
||||
/// For procedural NPCs: assigned by the 30/50/20 ratio via SimRng.
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum EntanglementTag {
|
||||
Flat,
|
||||
Mundane,
|
||||
Intrigue,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Axis 1: Want (D-024)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -421,5 +421,197 @@ fn spawn_real_content_with_relationships_and_secrets() {
|
||||
assert_eq!(nils_want.primary, npc::WantKind::Power);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test: EntanglementTag assignment from authored content (#176, D-029)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn entanglement_tags_assigned_from_triangle_membership() {
|
||||
let root = content_root();
|
||||
if !root.join("content.yaml").exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
world.init_resource::<EntityRegistry>();
|
||||
world.init_resource::<settled_reach_server::knowledge::ContentEntityRegistry>();
|
||||
world.init_resource::<settled_reach_server::npc::relationships::RelationshipGraph>();
|
||||
|
||||
let store = load_content(&root).expect("content loading should succeed");
|
||||
let result = spawn_content(&mut world, &store);
|
||||
|
||||
// Acceptance: 17+ NPCs spawn (we have 23 authored profiles)
|
||||
assert!(
|
||||
result.npcs_spawned >= 17,
|
||||
"Expected 17+ NPCs, got {}",
|
||||
result.npcs_spawned
|
||||
);
|
||||
|
||||
let registry = world.resource::<EntityRegistry>();
|
||||
|
||||
// Every NPC must have an EntanglementTag
|
||||
let mut intrigue_count = 0u32;
|
||||
let mut flat_count = 0u32;
|
||||
for (canonical_id, stable_id) in &result.npc_ids {
|
||||
let entity = registry
|
||||
.to_entity(stable_id)
|
||||
.unwrap_or_else(|| panic!("{} should have an entity", canonical_id));
|
||||
let tag = world
|
||||
.get::<npc::EntanglementTag>(entity)
|
||||
.unwrap_or_else(|| panic!("{} must have EntanglementTag", canonical_id));
|
||||
match tag {
|
||||
npc::EntanglementTag::Intrigue => intrigue_count += 1,
|
||||
npc::EntanglementTag::Flat => flat_count += 1,
|
||||
npc::EntanglementTag::Mundane => {} // reserved for procedural NPCs
|
||||
}
|
||||
}
|
||||
|
||||
// Acceptance: at least one EntanglementTag::Intrigue entity
|
||||
assert!(
|
||||
intrigue_count >= 1,
|
||||
"At least one NPC must be EntanglementTag::Intrigue, got 0"
|
||||
);
|
||||
|
||||
// Stronger assertion: we know 13 authored NPCs have non-empty triangle_membership
|
||||
assert!(
|
||||
intrigue_count >= 10,
|
||||
"Expected 10+ Intrigue NPCs (authored triangle members), got {}",
|
||||
intrigue_count
|
||||
);
|
||||
|
||||
// Some NPCs should be Flat (no triangle membership)
|
||||
assert!(
|
||||
flat_count >= 1,
|
||||
"At least one NPC should be EntanglementTag::Flat, got 0"
|
||||
);
|
||||
|
||||
// Spot-check: Kael (triangle member) must be Intrigue
|
||||
let kael_entity = registry
|
||||
.to_entity(&result.npc_ids["npc:kael-davan"])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
*world.get::<npc::EntanglementTag>(kael_entity).unwrap(),
|
||||
npc::EntanglementTag::Intrigue,
|
||||
"Kael (triangle member) must be Intrigue"
|
||||
);
|
||||
|
||||
// Spot-check: Devra (empty triangle_membership) must be Flat
|
||||
let devra_entity = registry
|
||||
.to_entity(&result.npc_ids["npc:devra"])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
*world.get::<npc::EntanglementTag>(devra_entity).unwrap(),
|
||||
npc::EntanglementTag::Flat,
|
||||
"Devra (no triangle membership) must be Flat"
|
||||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Test: Authored triangle instantiation (#188, D-087)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn authored_triangles_instantiated_from_content() {
|
||||
use settled_reach_server::content::template::{TriangleClassification, TriangleState};
|
||||
use settled_reach_server::simulation::tier::ActiveSim;
|
||||
|
||||
let root = content_root();
|
||||
if !root.join("content.yaml").exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
world.init_resource::<EntityRegistry>();
|
||||
world.init_resource::<settled_reach_server::knowledge::ContentEntityRegistry>();
|
||||
world.init_resource::<settled_reach_server::npc::relationships::RelationshipGraph>();
|
||||
|
||||
let store = load_content(&root).expect("content loading should succeed");
|
||||
let result = spawn_content(&mut world, &store);
|
||||
|
||||
// Query all TriangleState entities (clone to release world borrow)
|
||||
let triangles: Vec<TriangleState> = {
|
||||
let mut q = world.query::<&TriangleState>();
|
||||
q.iter(&world).cloned().collect()
|
||||
};
|
||||
|
||||
// Acceptance: exactly 5 authored triangles
|
||||
assert_eq!(
|
||||
triangles.len(),
|
||||
5,
|
||||
"Expected 5 authored triangles, got {}",
|
||||
triangles.len()
|
||||
);
|
||||
|
||||
// Count by classification (D-087)
|
||||
let active_count = triangles
|
||||
.iter()
|
||||
.filter(|t| t.classification == TriangleClassification::ActiveFork)
|
||||
.count();
|
||||
let passive_count = triangles
|
||||
.iter()
|
||||
.filter(|t| t.classification == TriangleClassification::PassiveTension)
|
||||
.count();
|
||||
|
||||
assert_eq!(
|
||||
active_count, 3,
|
||||
"Expected 3 ActiveFork triangles, got {}",
|
||||
active_count
|
||||
);
|
||||
assert_eq!(
|
||||
passive_count, 2,
|
||||
"Expected 2 PassiveTension triangles, got {}",
|
||||
passive_count
|
||||
);
|
||||
|
||||
// All 5 must have exactly 3 role assignments (triangle = 3 NPCs)
|
||||
for triangle in &triangles {
|
||||
assert_eq!(
|
||||
triangle.role_assignments.len(),
|
||||
3,
|
||||
"Triangle {:?} should have 3 role assignments, got {}",
|
||||
triangle.triangle_id,
|
||||
triangle.role_assignments.len()
|
||||
);
|
||||
}
|
||||
|
||||
// All role assignments must point to valid NPC entities in the registry
|
||||
let registry = world.resource::<EntityRegistry>();
|
||||
for triangle in &triangles {
|
||||
for (role, stable_id) in &triangle.role_assignments {
|
||||
assert!(
|
||||
registry.to_entity(stable_id).is_some(),
|
||||
"Triangle {:?} role '{}' points to StableId {:?} with no entity",
|
||||
triangle.triangle_id,
|
||||
role.0,
|
||||
stable_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// All triangle entities must have ActiveSim marker
|
||||
let mut active_query = world.query::<(&TriangleState, &ActiveSim)>();
|
||||
let active_triangles: Vec<_> = active_query.iter(&world).collect();
|
||||
assert_eq!(
|
||||
active_triangles.len(),
|
||||
5,
|
||||
"All 5 triangles must have ActiveSim marker"
|
||||
);
|
||||
|
||||
// Verify StableIds in role assignments correspond to spawned NPC canonical_ids
|
||||
let all_npc_stable_ids: std::collections::BTreeSet<_> =
|
||||
result.npc_ids.values().copied().collect();
|
||||
for triangle in &triangles {
|
||||
for (role, stable_id) in &triangle.role_assignments {
|
||||
assert!(
|
||||
all_npc_stable_ids.contains(stable_id),
|
||||
"Triangle {:?} role '{}' StableId {:?} not in spawned NPC set",
|
||||
triangle.triangle_id,
|
||||
role.0,
|
||||
stable_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime validation test (boot + tick 10 + snapshot) moved to
|
||||
// server/tests/content_runtime.rs per architectural review.
|
||||
|
||||
@@ -15,8 +15,8 @@ use bevy_ecs::{schedule::Schedule, world::World};
|
||||
use settled_reach_server::{
|
||||
content::template::{
|
||||
apply_resolve_triangle, tick_triangle_escalation, ResolveTriangleCommand,
|
||||
ResolveTriangleQueue, TemplateId, TriangleCrisisEventQueue, TriangleDef, TriangleId,
|
||||
TrianglePhase, TriangleState,
|
||||
ResolveTriangleQueue, TemplateId, TriangleClassification, TriangleCrisisEventQueue,
|
||||
TriangleDef, TriangleId, TrianglePhase, TriangleState,
|
||||
},
|
||||
knowledge::{registry::EntityRegistry, types::StableId, StableEntityId},
|
||||
npc::ToleranceThreshold,
|
||||
@@ -68,6 +68,7 @@ fn spawn_triangle(
|
||||
phase,
|
||||
tension_rate,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::default(),
|
||||
},
|
||||
))
|
||||
.id()
|
||||
@@ -329,6 +330,7 @@ fn d026_non_active_tier_triangle_not_escalated() {
|
||||
phase: TrianglePhase::Simmering,
|
||||
tension_rate: 5,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::default(),
|
||||
})
|
||||
.id();
|
||||
|
||||
@@ -395,6 +397,7 @@ fn resolve_command_sets_phase_to_resolved() {
|
||||
phase: TrianglePhase::Active,
|
||||
tension_rate: 3,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::default(),
|
||||
})
|
||||
.id();
|
||||
|
||||
@@ -423,6 +426,7 @@ fn d089_resolve_does_not_cascade() {
|
||||
phase: TrianglePhase::Active,
|
||||
tension_rate: 3,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::default(),
|
||||
})
|
||||
.id();
|
||||
|
||||
@@ -434,6 +438,7 @@ fn d089_resolve_does_not_cascade() {
|
||||
phase: TrianglePhase::Simmering,
|
||||
tension_rate: 2,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::default(),
|
||||
})
|
||||
.id();
|
||||
|
||||
@@ -445,6 +450,7 @@ fn d089_resolve_does_not_cascade() {
|
||||
phase: TrianglePhase::Active,
|
||||
tension_rate: 4,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::default(),
|
||||
})
|
||||
.id();
|
||||
|
||||
@@ -485,6 +491,7 @@ fn resolve_twice_is_idempotent() {
|
||||
phase: TrianglePhase::Active,
|
||||
tension_rate: 1,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::default(),
|
||||
})
|
||||
.id();
|
||||
|
||||
@@ -602,6 +609,7 @@ fn d087_all_v01_conflict_types_produce_escalatable_states() {
|
||||
phase: TrianglePhase::Simmering,
|
||||
tension_rate: 3,
|
||||
template_id: TemplateId(1),
|
||||
classification: TriangleClassification::default(),
|
||||
};
|
||||
assert_eq!(
|
||||
state.phase,
|
||||
|
||||
Reference in New Issue
Block a user