From f2c51a10fc0ec30ca1cb81641bda4c7b43f12d0c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 26 Aug 2026 05:48:20 +0200 Subject: [PATCH] Derive strategic course order from locations --- crates/ghostlight-dungeon/src/kernel.rs | 208 +++++++++++++++++- crates/ghostlight-dungeon/src/persona.rs | 13 +- .../ghostlight-transition-algebra.md | 25 +++ 3 files changed, 231 insertions(+), 15 deletions(-) diff --git a/crates/ghostlight-dungeon/src/kernel.rs b/crates/ghostlight-dungeon/src/kernel.rs index 0a39d05..c5cf958 100644 --- a/crates/ghostlight-dungeon/src/kernel.rs +++ b/crates/ghostlight-dungeon/src/kernel.rs @@ -2148,6 +2148,57 @@ fn merge_strategic_outcome_event_context( Ok(()) } +fn strategic_activity_phase( + campaign: &Campaign, + prospective_actor_locations: &BTreeMap, + prospective_gestalt_locations: &BTreeMap, + prospective_member_locations: &BTreeMap, + subject_id: &str, + location_ids: &[String], +) -> Result { + let (origin, destination) = if let Some(actor) = campaign.actors.get(subject_id) { + ( + actor.location_id.as_str(), + prospective_actor_locations + .get(subject_id) + .map(String::as_str), + ) + } else if let Some(gestalt) = campaign.gestalts.get(subject_id) { + ( + gestalt.home_location_id.as_str(), + prospective_gestalt_locations + .get(subject_id) + .map(String::as_str), + ) + } else if let Some(member_id) = subject_id.strip_prefix("member:") { + let origin = crate::resolution::dormant_member_location(campaign, member_id) + .map_err(|error| KernelError::Invalid(error.to_string()))?; + return Ok(relative_activity_phase( + &origin, + prospective_member_locations + .get(member_id) + .map(String::as_str), + location_ids, + )); + } else { + return Ok(1); + }; + Ok(relative_activity_phase(origin, destination, location_ids)) +} + +fn relative_activity_phase(origin: &str, destination: Option<&str>, location_ids: &[String]) -> u8 { + let Some(destination) = destination.filter(|destination| *destination != origin) else { + return 1; + }; + if location_ids.is_empty() || location_ids.iter().all(|location| location == origin) { + 0 + } else if location_ids.iter().all(|location| location == destination) { + 2 + } else { + 1 + } +} + fn apply_strategic_tick_plan( campaign: &mut Campaign, plan: crate::domain::StrategicTickPlan, @@ -2229,6 +2280,7 @@ fn apply_strategic_tick_plan( let revision = campaign.revision + 1; let at = campaign.world_time + Duration::hours(i64::from(campaign.tick_hours)); let mut events = Vec::new(); + let mut event_phases = BTreeMap::new(); let mut seen_institutions = BTreeSet::new(); for action in plan.institution_actions { if !seen_institutions.insert(action.institution_id.clone()) { @@ -2449,7 +2501,15 @@ fn apply_strategic_tick_plan( .filter(|target| campaign.gestalts.contains_key(*target)) .cloned(), ); - events.push(crate::domain::Event { + let phase = strategic_activity_phase( + campaign, + &prospective_actor_locations, + &prospective_gestalt_locations, + &prospective_member_locations, + &action.gestalt_id, + &locations, + )?; + let event = crate::domain::Event { id: format!( "strategic:{revision}:gestalt-activity:{}:{}", action.gestalt_id, @@ -2463,7 +2523,9 @@ fn apply_strategic_tick_plan( gestalt_ids, location_ids: locations, public_channels: action.public_channels, - }); + }; + event_phases.insert(event.id.clone(), phase); + events.push(event); } let mut legacy_seen_actors = BTreeSet::new(); @@ -2595,7 +2657,15 @@ fn apply_strategic_tick_plan( .collect::>(); gestalt_ids.sort(); gestalt_ids.dedup(); - events.push(Event { + let phase = strategic_activity_phase( + campaign, + &prospective_actor_locations, + &prospective_gestalt_locations, + &prospective_member_locations, + &action.actor_id, + &action.location_ids, + )?; + let event = Event { id: format!( "strategic:{revision}:actor-activity:{}:{}", action.actor_id, @@ -2609,7 +2679,9 @@ fn apply_strategic_tick_plan( gestalt_ids, location_ids: action.location_ids, public_channels: action.public_channels, - }); + }; + event_phases.insert(event.id.clone(), phase); + events.push(event); } let mut legacy_seen_members = BTreeSet::new(); @@ -2695,7 +2767,15 @@ fn apply_strategic_tick_plan( ); gestalt_ids.sort(); gestalt_ids.dedup(); - events.push(Event { + let phase = strategic_activity_phase( + campaign, + &prospective_actor_locations, + &prospective_gestalt_locations, + &prospective_member_locations, + &format!("member:{}", action.member_id), + &action.location_ids, + )?; + let event = Event { id: format!( "strategic:{revision}:member-activity:{}:{}", action.member_id, @@ -2709,7 +2789,9 @@ fn apply_strategic_tick_plan( gestalt_ids, location_ids: action.location_ids, public_channels: action.public_channels, - }); + }; + event_phases.insert(event.id.clone(), phase); + events.push(event); } let mut seen_member_migrations = BTreeSet::new(); for action in plan.member_migrations { @@ -2807,7 +2889,15 @@ fn apply_strategic_tick_plan( .chars() .take(16) .collect::(); - events.push(Event { + let phase = strategic_activity_phase( + campaign, + &prospective_actor_locations, + &prospective_gestalt_locations, + &prospective_member_locations, + &source_subject_id, + &locations, + )?; + let event = Event { id: format!("strategic:{revision}:activity-outcome:{digest_suffix}"), at, kind: "strategic_activity_outcome".into(), @@ -2817,8 +2907,11 @@ fn apply_strategic_tick_plan( gestalt_ids, location_ids: locations, public_channels, - }); + }; + event_phases.insert(event.id.clone(), phase); + events.push(event); } + events.sort_by_key(|event| event_phases.get(&event.id).copied().unwrap_or(1)); *campaign = next; Ok(AppliedStrategicTickPlan { events, mutation }) } @@ -3539,6 +3632,23 @@ mod tests { format!("sha256:{:x}", Sha256::digest(label.as_bytes())) } + #[test] + fn strategic_activity_phase_is_derived_from_exact_locations() { + assert_eq!( + relative_activity_phase("yard", Some("room"), &["yard".into()]), + 0 + ); + assert_eq!( + relative_activity_phase("yard", Some("room"), &["room".into()]), + 2 + ); + assert_eq!( + relative_activity_phase("yard", Some("room"), &["yard".into(), "room".into()]), + 1 + ); + assert_eq!(relative_activity_phase("yard", None, &["yard".into()]), 1); + } + fn resolve_test_activities(mut plan: StrategicTickPlan) -> StrategicTickPlan { plan.activity_outcomes.extend( plan.gestalt_activities @@ -5809,7 +5919,14 @@ mod tests { id: "yard".into(), name: "Yard".into(), container_id: None, - routes: BTreeMap::new(), + routes: BTreeMap::from([( + "back".into(), + Route { + destination_id: "room".into(), + distance: "near".into(), + travel_minutes: 20, + }, + )]), persistent_features: vec![], }, ); @@ -5924,6 +6041,79 @@ mod tests { .iter() .any(|event| event.kind == "strategic_activity_outcome") ); + + let origin_activity = crate::domain::CellActionProposal { + subject_id: "runner".into(), + intent: "Repair the yard marker, then return to the room.".into(), + intended_effect: "Attempt the repair before leaving the yard.".into(), + priority: 50, + state_references: vec![], + public_channels: vec![], + effects: vec![ + crate::domain::StrategicCellEffect::ActorMove { + actor_id: "runner".into(), + destination_id: "room".into(), + }, + crate::domain::StrategicCellEffect::ActorActivity { + actor_id: "runner".into(), + activity: StrategicActivityKind::Prepare, + target_subject_ids: vec![], + location_ids: vec!["yard".into()], + }, + ], + }; + let action_digest = crate::resolution::cell_action_digest(&origin_activity).unwrap(); + let result = kernel + .command(WorldCommand::AdvanceStrategicTick { + expected_revision: 2, + source: TickSource::Scheduler, + plan: Some(StrategicTickPlan { + selected_actions: vec![origin_activity], + activity_outcomes: vec![StrategicActivityOutcome { + schema: "ghostlight.strategic_activity_outcome.v1".into(), + action_digest: action_digest.clone(), + source_subject_id: "runner".into(), + band: StrategicOutcomeBand::Mixed, + summary: "The yard repair remains provisional.".into(), + supporting_state_references: vec![], + effect: StrategicOutcomeEffect::NoMaterialChange { + reason: "The repair did not establish a durable state change.".into(), + }, + }], + ..StrategicTickPlan::default() + }), + model_receipt_hash: Some(format!("sha256:{}", "d".repeat(64))), + resolution_wave: None, + }) + .await + .unwrap(); + let CommandResult::Committed { campaign, .. } = result else { + panic!("expected commit") + }; + assert_eq!(campaign.actors["runner"].location_id, "room"); + let activity_index = campaign + .events + .iter() + .position(|event| event.id == "strategic:3:actor-activity:runner:prepare") + .unwrap(); + let outcome_suffix = action_digest + .strip_prefix("sha256:") + .unwrap() + .chars() + .take(16) + .collect::(); + let outcome_index = campaign + .events + .iter() + .position(|event| event.id == format!("strategic:3:activity-outcome:{outcome_suffix}")) + .unwrap(); + let movement_index = campaign + .events + .iter() + .position(|event| event.id == "strategic:3:actor:runner") + .unwrap(); + assert!(activity_index < outcome_index); + assert!(outcome_index < movement_index); } #[tokio::test] diff --git a/crates/ghostlight-dungeon/src/persona.rs b/crates/ghostlight-dungeon/src/persona.rs index 3066407..dd7c811 100644 --- a/crates/ghostlight-dungeon/src/persona.rs +++ b/crates/ghostlight-dungeon/src/persona.rs @@ -377,7 +377,7 @@ const CELL_APPRAISAL_OUTPUT_CONTRACT: &str = r#"{ } }"#; -const CELL_EFFECT_VERIFIER_INSTRUCTIONS: &str = "You are the private semantic verifier between an Interpreter and the world kernel. Judge this one candidate action's ordered typed effects as one composition against the exact attributed subject's choice in the Persona turn. Structural permissions were already checked. Use exact_subject_permission as the sole map of canonical subjects, locations, and destinations for this actor. activity_targets supplies each canonical target's exact name and current locations; reachable_destinations supplies exact actor-movement destination IDs and names; migration_destinations supplies exact population names and locations. Preserve every distinct chosen means: travel followed by a local attempt at that supplied destination requires both movement and activity in the same action, in that order. When the Persona chooses to go to a canonical target, actor_move must use that target's actual different reachable location. If actor and target are already co-located, reject movement to some other place; a local communicate, coordinate, or prepare may encode the stated attempt instead. A place named only in prose and absent from reachable_destinations and migration_destinations is local texture inside the supplied activity location; walking to it cannot justify rejecting a concrete local prepare or repair as omitted travel. Return exactly one verdict with action_index 0. A gestalt_migration means that exact population leaf chooses to travel together to the supplied destination within the strategic horizon; loading, waiting, giving away passage, sending only some other subject, or merely considering travel does not entail it. Conversely, when the population chooses to board, depart, or relocate together, reject gestalt_activity prepare that erases the chosen journey. Gestalt migration never entails that a named member moved. A member_migration means that named member personally chooses to travel to the destination. Boarding a transport whose supplied destination is unambiguous in the lived stream is a chosen journey; the Persona need not repeat the place name. Giving away a berth, sending somebody else, waiting, or merely considering travel does not entail migration. Conversely, when the member chooses to board, depart, travel, or join the supplied destination, reject member_activity that reduces that commitment to preparing, queuing, or approaching. A member_activity belongs only to that exact named person's stated attempt; it cannot be reassigned to their population. Communication targets must be the exact canonical subjects actually addressed in the Persona turn. An exact activity_targets entry is sufficient authority to attempt direct communication with that named subject; allowed_persistent_publication_channels governs only durable public publication and is never an additional requirement for direct contact. A cohesive Gestalt coordinating its own unnamed members uses a targetless local coordinate effect, including when the same action separately communicates with a named external target. If the Persona addresses an unnamed clerk, dock master, passerby, or local environment, reject any effect that substitutes a containing population, related institution, or merely permitted ID. A targetless local investigate at the subject's exact current or paired movement destination is the faithful supported shape for seeking information from an unnamed role or the environment; its empty target list is intentional and must not itself be grounds for rejection. An institution posture must express its stated commitment or withholding. A gestalt pressure resolution must be causally supported by its stated attempt, and an added pressure must be a resulting unresolved condition rather than completed-action prose. An activity records only the exact attempt—never successful preparation, coordination, discovery, recruitment, obstruction, exchange, delivery, persuasion, acceptance, or target response. Reject omissions, reversals, subject swaps, wrong destinations, wishful outcomes, and effects that the Persona did not choose. Be concise. Return exactly one JSON object. A faithful verdict uses result \"match\", null mismatch_kind, and null repair_guidance. Otherwise use result \"mismatch\", exactly one mismatch_kind (\"subject_swap\", \"effect_omission\", \"effect_reversal\", \"target_substitution\", \"invented_outcome\", or \"wrong_effect_kind\"), and one concrete repair_guidance sentence of at most 240 characters. Name the exact omitted choice, substituted target, or wrong destination. When no supplied typed effect composition can faithfully encode the choice, explicitly say to remove the action rather than downgrade or redirect it. Shape: {\"verdicts\":[{\"action_index\":0,\"result\":\"match\",\"mismatch_kind\":null,\"repair_guidance\":null}]}"; +const CELL_EFFECT_VERIFIER_INSTRUCTIONS: &str = "You are the private semantic verifier between an Interpreter and the world kernel. Judge this one candidate action's typed effects as one composition against the exact attributed subject's choice in the Persona turn. Structural permissions were already checked. Use exact_subject_permission as the sole map of canonical subjects, locations, and destinations for this actor. activity_targets supplies each canonical target's exact name and current locations; reachable_destinations supplies exact actor-movement destination IDs and names; migration_destinations supplies exact population names and locations. Preserve every distinct chosen means. When one choice contains relocation, an activity at the subject's exact snapshot location occurs before relocation and an activity at the exact admitted destination occurs after arrival; activities within one location phase are an unordered atomic set. Array and object field order are not chronology. When the Persona chooses to go to a canonical target, actor_move must use that target's actual different reachable location. If actor and target are already co-located, reject movement to some other place; a local communicate, coordinate, or prepare may encode the stated attempt instead. A place named only in prose and absent from reachable_destinations and migration_destinations is local texture inside the supplied activity location; walking to it cannot justify rejecting a concrete local prepare or repair as omitted travel. Return exactly one verdict with action_index 0. A gestalt_migration means that exact population leaf chooses to travel together to the supplied destination within the strategic horizon; loading, waiting, giving away passage, sending only some other subject, or merely considering travel does not entail it. Conversely, when the population chooses to board, depart, or relocate together, reject gestalt_activity prepare that erases the chosen journey. Gestalt migration never entails that a named member moved. A member_migration means that named member personally chooses to travel to the destination. Boarding a transport whose supplied destination is unambiguous in the lived stream is a chosen journey; the Persona need not repeat the place name. Giving away a berth, sending somebody else, waiting, or merely considering travel does not entail migration. Conversely, when the member chooses to board, depart, travel, or join the supplied destination, reject member_activity that reduces that commitment to preparing, queuing, or approaching. A member_activity belongs only to that exact named person's stated attempt; it cannot be reassigned to their population. Communication targets must be the exact canonical subjects actually addressed in the Persona turn. An exact activity_targets entry is sufficient authority to attempt direct communication with that named subject; allowed_persistent_publication_channels governs only durable public publication and is never an additional requirement for direct contact. A cohesive Gestalt coordinating its own unnamed members uses a targetless local coordinate effect, including when the same action separately communicates with a named external target. If the Persona addresses an unnamed clerk, dock master, passerby, or local environment, reject any effect that substitutes a containing population, related institution, or merely permitted ID. A targetless local investigate at the subject's exact current or paired movement destination is the faithful supported shape for seeking information from an unnamed role or the environment; its empty target list is intentional and must not itself be grounds for rejection. An institution posture must express its stated commitment or withholding. A gestalt pressure resolution must be causally supported by its stated attempt, and an added pressure must be a resulting unresolved condition rather than completed-action prose. An activity records only the exact attempt—never successful preparation, coordination, discovery, recruitment, obstruction, exchange, delivery, persuasion, acceptance, or target response. Reject omissions, reversals, subject swaps, wrong destinations, wishful outcomes, and effects that the Persona did not choose. Be concise. Return exactly one JSON object. A faithful verdict uses result \"match\", null mismatch_kind, and null repair_guidance. Otherwise use result \"mismatch\", exactly one mismatch_kind (\"subject_swap\", \"effect_omission\", \"effect_reversal\", \"target_substitution\", \"invented_outcome\", or \"wrong_effect_kind\"), and one concrete repair_guidance sentence of at most 240 characters. Name the exact omitted choice, substituted target, or wrong destination. When no supplied typed effect composition can faithfully encode the choice, explicitly say to remove the action rather than downgrade or redirect it. Shape: {\"verdicts\":[{\"action_index\":0,\"result\":\"match\",\"mismatch_kind\":null,\"repair_guidance\":null}]}"; const CELL_ACTIVITY_CLASSIFICATION_GUIDANCE: &str = "Classify the chosen means by what the subject actually does. communicate means speak, send, offer, ask, or notify; coordinate means arrange a joint attempt; investigate means inspect, examine, diagnose, measure, test, or assess an existing condition in order to learn; recruit means invite; trade means offer an exchange; obstruct means attempt interference. prepare means materially repair, build, arrange, or ready a bounded resource, capability, or plan. Merely inspecting a handcart, regulator, record, route, patient, or other existing condition before deciding what to do is investigate, not prepare. Only actual repair or readiness work is prepare."; @@ -1324,7 +1324,7 @@ impl CellProjectionEngine { let permission_guidance = format!( concat!( "Emit at most {} exact constituent- or named-member-attributed attempts. Priority is an urgency score from 0 to 100 where higher numbers resolve first. ", - "Each action carries an effects object whose exact subject-specific lane keys are supplied by the schema. Scalar lanes contain one typed effect. An activities lane is one object keyed by up to three distinct chosen activity kinds; each kind occurs at most once and its value contains the union of that means' exact targets and locations. Top-level lane names are stable across subjects: use null for a null-only unavailable lane and for any schema-required optional lane or activity key the subject does not use. A non-null lane or activity key absent from its exact schema is structurally unavailable: do not emit it and do not invent a destination. Pressure and migration lanes each have one slot. When one chosen act includes travel followed by local attempts at the supplied destination, preserve every means in one action by filling movement and the keyed activities object. Runtime lowers movement before local activity; the activity keys are an atomic set rather than a second timeline. Never split one subject's single choice into multiple actions. ", + "Each action carries an effects object whose exact subject-specific lane keys are supplied by the schema. Scalar lanes contain one typed effect. An activities lane is one object keyed by up to three distinct chosen activity kinds; each kind occurs at most once and its value contains the union of that means' exact targets and locations. Top-level lane names are stable across subjects: use null for a null-only unavailable lane and for any schema-required optional lane or activity key the subject does not use. A non-null lane or activity key absent from its exact schema is structurally unavailable: do not emit it and do not invent a destination. Pressure and migration lanes each have one slot. Preserve every means of one chosen course in one action. With relocation, activities at the exact snapshot location occur before departure and activities at the exact admitted destination occur after arrival; activity keys inside each location phase are an atomic set. Field order is not chronology. Never split one subject's single choice into multiple actions. ", "Use gestalt_activities or member_activities for concrete attempts that do not themselves change pressure. A cohesive Gestalt coordinating its own unnamed internal members uses coordinate with an empty target_subject_ids list; do not invent its containing population, a distant population, or another canonical subject as the target of internal coordination. Cite the smallest exact set of state_references that materially supports each attempt; the permission list is an upper bound, not a checklist to echo. ", "target_subject_ids and location_ids must come from that exact subject's permissions. activity_targets is the exact canonical target map: each key is the authoritative ID and each value supplies the target's name and current canonical locations. Use an ID only when the Persona addresses that named target, never merely because the ID is permitted. If an addressed person or role has no matching activity_targets entry, it is not a canonical target in this slice. reachable_destinations maps exact actor-movement destination IDs to names. migration_destinations maps exact population destination IDs to names and locations. When the Persona chooses to go to a canonical target, compare the target's current locations with the acting subject's current location and exact reachable destinations; never guess a destination from an opaque ID. Every activity has at most four unique target_subject_ids; choose the four most causally relevant when more permitted subjects are involved. A member activity uses exactly the member's source_location_id. Internal work is prepare with no targets. A local investigate may have no target and use the exact current location to seek information from the environment or an unnamed ordinary role; asking an unnamed clerk or dock master for facts maps here and records only the inquiry, never a reply or discovery. A local communicate may likewise have no target at the exact current location when the Persona speaks, sends, offers, asks permission, or notifies an unnamed ordinary role; it records only the source's outgoing attempt, never a listener, reply, acceptance, or outcome. Communication with a canonical subject requires that exact target ID. Never substitute a containing population, related institution, or merely permitted ID for an unnamed role. ", "Write intended_effect as the attempted act, never its hoped-for outcome or target response. Merely waiting, watching, staying, holding position, or remaining ready is attributed inaction, not prepare. prepare requires concrete work on a bounded arrangement, repair, resource, or capability-backed readiness change. Institution posture must be a specific materially new commitment or withholding of at most 240 characters. already_committed_posture is state already in force: maintaining, continuing, or restating it is inaction and must not emit an institution action. Gestalt pressure_resolutions copy exact current_pressures; additions are new unresolved constraints, never completed actions. Use only permitted state references. public_channels means durable publication of this attempt through exact allowed_persistent_publication_channels; it is not a perception method or ordinary local speech. Use [] when that exact list is empty. ", @@ -1584,8 +1584,9 @@ async fn run_cell_effect_verifier_wave( let exact_subject_permission = cell_action_verifier_permission(slice, &action.subject_id)?; let verifier_context = serde_json::json!({ "effect_order_contract":{ - "activity_effects":"unordered_atomic_set", - "cross_lane_order":"movement_or_migration_before_activity_at_destination" + "activity_effects_within_one_location":"unordered_atomic_set", + "cross_location_order":"snapshot_location_activity_then_relocation_then_destination_activity", + "field_order":"not_chronology" }, "local_attempt_contract":"A targetless local communicate at the source's exact current location faithfully records speech, an offer, a permission request, or a notice directed to an unnamed ordinary role. A targetless local obstruct there faithfully records attempted interference with unnamed infrastructure, terrain, traffic, or another local feature. Both record only the source's attempt—never a listener, reply, damage, disruption, acceptance, or outcome—and must not be rejected merely because target_subject_ids is empty.", "spatial_effect_contract":"A prepare, investigate, or other activity may include incidental walking, approaching, queuing, carrying, or repositioning around an unnamed local feature while the source remains inside the effect's supplied canonical location. The activity records the attempt and need not serialize every footstep. Reject omitted movement only when the Persona clearly commits the subject to a different supplied canonical location or population destination; local texture does not create topology or establish arrival.", @@ -1609,7 +1610,7 @@ async fn run_cell_effect_verifier_wave( std::slice::from_ref(action), )?, lived_stream: format!( - "{CELL_EFFECT_VERIFIER_INSTRUCTIONS}\n\nEFFECT ORDER CONTRACT: Ordering exists only across lanes: movement or migration precedes an activity performed at its destination. Distinct effects inside the activity lane are one unordered atomic set. Their serialized array order is not chronology and cannot support an effect_reversal verdict.\n\nCONTEXT:\n{}", + "{CELL_EFFECT_VERIFIER_INSTRUCTIONS}\n\nEFFECT ORDER CONTRACT: Exact activity locations derive the course. Snapshot-location activity precedes relocation; exact-destination activity follows arrival. Distinct activity kinds inside one location phase are an unordered atomic set. Serialized field and array order is not chronology and cannot support an effect_reversal verdict.\n\nCONTEXT:\n{}", serde_json::to_string(&verifier_context)? ), output_schema: Some(verifier_schema.clone()), @@ -1682,7 +1683,7 @@ fn append_cell_correction( ) { let repair_guidance = cell_correction_guidance(error); request.lived_stream.push_str(&format!( - "\n\nCORRECTION TASK—THE PREVIOUS APPRAISAL WAS REJECTED.\nREJECTION: {error}\nPREVIOUS_REJECTED_APPRAISAL:\n{rejected_appraisal}\nReturn one corrected complete appraisal against the same snapshot, lived stream, Persona turn, and exact permission context. The semantic verifier's concrete repair guidance in REJECTION names the exact mismatch and is the primary correction instruction. {repair_guidance} {CELL_ACTIVITY_CLASSIFICATION_GUIDANCE} Every retained action must still carry one to four non-null orthogonal effect slots under the original contract. Preserve all distinct chosen means in one action; travel followed by a local attempt at the exact destination fills movement and activity, which runtime lowers in that order. If an institution merely continues or restates already_committed_posture, move that exact voiced subject to inactions; it is holding steady. If the Persona chose travel but that subject has no exact permitted destination in reachable_destinations or migration_destinations, no movement transition is available: remove that action and record attributed inaction only when the Persona explicitly holds or waits without making another attempt. If an attempted preparation, investigation, request, or deliberation has no permitted typed consequence, remove it and record attributed inaction only when the Persona explicitly holds or waits without making another attempt; never emit an empty transition or upgrade consideration into a completed consequence. Never add inaction for an unvoiced subject. Keep each reason within 160 characters." + "\n\nCORRECTION TASK—THE PREVIOUS APPRAISAL WAS REJECTED.\nREJECTION: {error}\nPREVIOUS_REJECTED_APPRAISAL:\n{rejected_appraisal}\nReturn one corrected complete appraisal against the same snapshot, lived stream, Persona turn, and exact permission context. The semantic verifier's concrete repair guidance in REJECTION names the exact mismatch and is the primary correction instruction. {repair_guidance} {CELL_ACTIVITY_CLASSIFICATION_GUIDANCE} Every retained action must still carry one to four non-null orthogonal effect slots under the original contract. Preserve all distinct chosen means in one action. With relocation, snapshot-location activities occur before departure and exact-destination activities occur after arrival; field order is not chronology. If an institution merely continues or restates already_committed_posture, move that exact voiced subject to inactions; it is holding steady. If the Persona chose travel but that subject has no exact permitted destination in reachable_destinations or migration_destinations, no movement transition is available: remove that action and record attributed inaction only when the Persona explicitly holds or waits without making another attempt. If an attempted preparation, investigation, request, or deliberation has no permitted typed consequence, remove it and record attributed inaction only when the Persona explicitly holds or waits without making another attempt; never emit an empty transition or upgrade consideration into a completed consequence. Never add inaction for an unvoiced subject. Keep each reason within 160 characters." )); } diff --git a/docs/architecture/ghostlight-transition-algebra.md b/docs/architecture/ghostlight-transition-algebra.md index 3ac4b00..429e36f 100644 --- a/docs/architecture/ghostlight-transition-algebra.md +++ b/docs/architecture/ghostlight-transition-algebra.md @@ -303,6 +303,31 @@ not: every parent resource must be assigned to exactly one child in the approved preview, so aggregation cannot mint a granary for every ideological subgroup. +### Within-horizon course ordering + +A strategic choice is one atomic endpoint transaction, but its observable +means may straddle one admitted relocation. `WorldKernel` derives the only +within-horizon ordering from exact occupancy and effect locations: + +1. activities at the subject's snapshot location occur before relocation; +2. the admitted relocation or population migration occurs next; +3. activities at the exact admitted destination occur after arrival. + +Distinct activity kinds inside one location phase remain an unordered atomic +set. Activities spanning several locations without one unambiguous phase also +remain simultaneous at the strategic resolution of that subject. The model +does not emit phase labels, sequence numbers, or prose-authored timing; effect +array and object field order are not authority. An unrelated activity location +still fails exact spatial validation. + +This derived order owns chronological event lowering only. The mutation batch +still owns one indivisible final state, and every permit is compiled from the +same pre-wave snapshot. A failed phase, outcome, or mutation therefore aborts +the complete wave rather than leaving a subject halfway through its course. +The old blanket assumption that every movement precedes every activity is not +an owner and must not survive in Projector, Interpreter, verifier, or kernel +guidance. + Foreground assessment uses the same authority-shaped contract principle. Its model schema contains only mutation lanes available in the current snapshot: no route means no movement lane, no accessible undisclosed proposition means