diff --git a/crates/ghostlight-dungeon/src/bin/ghostlight-gestalt-dynamics-smoke.rs b/crates/ghostlight-dungeon/src/bin/ghostlight-gestalt-dynamics-smoke.rs index 05acb85..b0fbf65 100644 --- a/crates/ghostlight-dungeon/src/bin/ghostlight-gestalt-dynamics-smoke.rs +++ b/crates/ghostlight-dungeon/src/bin/ghostlight-gestalt-dynamics-smoke.rs @@ -104,6 +104,11 @@ async fn main() -> anyhow::Result<()> { .unwrap_or_else(|_| "gestalt-dynamics-refugee-return".into()); let require_migration = std::env::var("GHOSTLIGHT_LIVE_FIRE_REQUIRE_MIGRATION") .is_ok_and(|value| value == "1" || value.eq_ignore_ascii_case("true")); + let fairness_stress_waves = std::env::var("GHOSTLIGHT_LIVE_FIRE_FAIRNESS_WAVES") + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| (1..=31).contains(value)) + .unwrap_or_default(); let root = std::env::var_os("GHOSTLIGHT_LIVE_FIRE_RESULT_ROOT") .map(PathBuf::from) .unwrap_or_else(|| { @@ -115,6 +120,11 @@ async fn main() -> anyhow::Result<()> { }); std::fs::create_dir_all(&root)?; let campaign = dynamics_campaign(); + let source_lineage_depth = lineage_depth(&campaign, "refugees-east")?; + let destination_lineage_depth = lineage_depth(&campaign, "harbor-neighbors")?; + if source_lineage_depth < 2 || destination_lineage_depth < 2 { + anyhow::bail!("refugee callback fixture lost its nested lineage depth") + } let player_before = campaign.actors[&campaign.player_actor_id].clone(); let member_before = campaign.gestalt_members["mira-venn"].clone(); let capabilities_before = effective_member_capabilities(&campaign, "mira-venn")?; @@ -178,6 +188,14 @@ async fn main() -> anyhow::Result<()> { { anyhow::bail!("whole-setting arena lost a rival population or canonical subject") } + if root_cell.subject_ids.iter().any(|subject_id| { + campaign + .agency_profiles + .get(subject_id) + .is_none_or(|profile| !profile.active_leaf || !profile.simulation_eligible) + }) { + anyhow::bail!("whole-setting arena simulated an inactive lineage parent") + } let migration_proposal = output .wave .appraisals @@ -252,6 +270,8 @@ async fn main() -> anyhow::Result<()> { "effective_budget":output.wave.cover.effective_budget, "cell_mode":root_cell.mode, "rivals_share_arena":true, + "source_lineage_depth":source_lineage_depth, + "destination_lineage_depth":destination_lineage_depth, "choice":"explicit_inaction", "migration_proposal":serde_json::Value::Null, "initial_background_action_count":background_action_count, @@ -290,7 +310,13 @@ async fn main() -> anyhow::Result<()> { let mut sustained_waves = Vec::new(); let mut background_subject_ids = BTreeSet::new(); let mut detail_focus_subject_ids = BTreeSet::new(); - for (wave_index, budget) in [4_u8, 8, 4].into_iter().enumerate() { + detail_focus_subject_ids.extend(root_cell.detail_focus_subject_id.clone()); + let sustained_budgets = if fairness_stress_waves == 0 { + vec![4_u8, 8, 4] + } else { + vec![1_u8; fairness_stress_waves] + }; + for (wave_index, budget) in sustained_budgets.into_iter().enumerate() { let control = kernel .command(WorldCommand::SetResolutionBudget { expected_revision: advanced.revision, @@ -403,6 +429,21 @@ async fn main() -> anyhow::Result<()> { background_subject_ids.len() ) } + let fairness_missing_subject_ids = if fairness_stress_waves == 0 { + BTreeSet::new() + } else { + root_cell + .subject_ids + .difference(&detail_focus_subject_ids) + .cloned() + .collect::>() + }; + if !fairness_missing_subject_ids.is_empty() { + anyhow::bail!( + "budget-one fairness stress omitted debt focus for {:?}", + fairness_missing_subject_ids + ) + } let member_after_sustained = &advanced.gestalt_members["mira-venn"]; if member_after_sustained.gestalt_id != "harbor-neighbors" || member_after_sustained.relationships != member_before.relationships @@ -482,6 +523,10 @@ async fn main() -> anyhow::Result<()> { "initial_background_action_count":background_action_count, "sustained_background_subject_ids":background_subject_ids, "sustained_detail_focus_subject_ids":detail_focus_subject_ids, + "fairness_stress_waves":fairness_stress_waves, + "fairness_missing_subject_ids":fairness_missing_subject_ids, + "source_lineage_depth":source_lineage_depth, + "destination_lineage_depth":destination_lineage_depth, "sustained_waves":sustained_waves, "plan":plan, "commit":committed, @@ -506,6 +551,31 @@ async fn main() -> anyhow::Result<()> { Ok(()) } +#[cfg(windows)] +fn lineage_depth( + campaign: &ghostlight_dungeon::domain::Campaign, + leaf_id: &str, +) -> anyhow::Result { + use std::collections::BTreeSet; + + let mut current = leaf_id; + let mut depth = 0; + let mut seen = BTreeSet::new(); + while let Some(lineage) = campaign.gestalt_lineages.values().find(|lineage| { + lineage + .child_gestalt_ids + .iter() + .any(|child| child == current) + }) { + if !seen.insert(lineage.parent_gestalt_id.as_str()) { + anyhow::bail!("gestalt lineage contains a cycle at {current}") + } + depth += 1; + current = &lineage.parent_gestalt_id; + } + Ok(depth) +} + #[cfg(windows)] fn dynamics_campaign() -> ghostlight_dungeon::domain::Campaign { use chrono::{Duration, Utc}; diff --git a/docs/architecture/ghostlight-multiresolution-agency.md b/docs/architecture/ghostlight-multiresolution-agency.md index 51c0e78..1f9bae4 100644 --- a/docs/architecture/ghostlight-multiresolution-agency.md +++ b/docs/architecture/ghostlight-multiresolution-agency.md @@ -488,6 +488,15 @@ the 1,000-subject target. Pressure goldens preserve geography under blockade, ideology under schism, workplace/economic role under strike, species/body and transport under epidemic, and information boundaries under espionage. +The live Gestalt-dynamics harness has two modes. Its golden mode traverses +budgets 1 → 4 → 8 → 4 and proves independent population/member migration plus +automatic rematerialization of the same durable person. Its fairness mode keeps +the whole 24-subject setting in one arena for 24 total strategic ticks. It +requires every runtime-selected detail focus within the bound, rejects inactive +lineage parents as simulation subjects, and repeats the identity/callback check +after sustained compression. Both migration endpoints must be active leaves at +lineage depth two or greater. + The Starfire deployment smoke ran a live provider-backed wave over three eligible singleton cells, committed three offscreen events and three channel-accessible news items in 19.55 seconds, and preserved the absent player. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index dcbda3a..b53483d 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -200,6 +200,17 @@ genuinely new but over-240-character posture twice. The existing canonical bound is now present in both the compact Interpreter contract and generated JSON Schema, and prompt guidance names it before generation. +`gestalt-posture-bea5d6c-2` is the first complete strict golden. One budget-1 +rival arena covered 24 subjects; budgets then changed 4 → 8 → 4. The run +committed 26 actions across 11 distinct background subjects, preserved Mira's +identity through independent member and population journeys, and automatically +rematerialized the same person at South Harbor. It completed in 90.345 seconds +with 146,509 prompt tokens, a 40.97% cache-hit ratio, and 5,635 prompt tokens per +committed action. Projector cache hit was 53.82%. The new stress mode can keep +budget 1 for 23 additional waves, require all 24 debt-selected foci across the +initial plus sustained ticks, reject inactive lineage parents, and repeat the +callback after compression. + The multiresolution body is deployed on Starfire. Typed health matched the immutable release, DeepSeek was ready, and the disposable live strategic-cell smoke committed three offscreen events plus three accessible news items without diff --git a/state/map.yaml b/state/map.yaml index 6ea716a..41a086b 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -267,6 +267,6 @@ data_flow: - If state grows faster than understanding, cut it back. current_status: - summary: GhostlightDungeon production remains on the verified Starfire release. The acceptance candidate is pressuring multiresolution agency: exact Gestalt activity attempts, typed Gestalt event participation, nested lineage identity preservation, location-bounded presence projection, per-action semantic verification, and automatic refugee callback rematerialization. Production state is untouched while the strict live-fire harness and receipts falsify the candidate. - next_action: Rerun the strict multiresolution refugee-return live fire with runtime-bound Projector perspectives and one ordered semantic verdict per action. Require meaningful whole-setting background activity, exact rival-arena attribution, independent population and named-member journeys, and automatic presence-planner rematerialization of the same durable member. Profile stage receipts for cache and cognitive efficiency, then pressure longer low-budget fairness waves and nested fission depth. + summary: GhostlightDungeon production remains on the verified Starfire release. The strict 24-subject multiresolution golden is complete: a budget-one rival arena, independent population/member migration, 26 background consequences across 11 subjects, stable identity through 1/4/8/4 resolution changes, and automatic rematerialization of the same refugee. Production state remains untouched. + next_action: Run the new 24-tick budget-one fairness stress. Require every canonical subject to receive runtime-selected debt focus, reject inactive lineage parents, preserve Mira through sustained aggregate activity, and rematerialize the same person. Then run the 1/4/8/32 scale matrix and compare cache and cognitive-efficiency profiles. gestalt_population: Gestalt Personas own shared population baselines; persistent member deltas own named individuality; WorldKernel materialization and approval-gated fission preserve lineage and identity. Presence projection is limited to nearby active leaves and exact-location dormant members. Simulation cells are derived covers only.