From e5ef5e22ea8e7446993f54603844e75e5af7c8bf Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 23 Aug 2026 01:07:58 +0200 Subject: [PATCH] Keep private assessment outside strategic catch-up --- crates/ghostlight-dungeon/src/main.rs | 64 ++++++++++++++++++--- docs/architecture/ghostlight-dungeon-mvp.md | 9 ++- notes/ghostlight-current-system-map.md | 6 +- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/crates/ghostlight-dungeon/src/main.rs b/crates/ghostlight-dungeon/src/main.rs index ab33540..60a6c98 100644 --- a/crates/ghostlight-dungeon/src/main.rs +++ b/crates/ghostlight-dungeon/src/main.rs @@ -4083,13 +4083,14 @@ async fn command( if let Err(error) = validate_player_http_command(&command) { return (StatusCode::UNPROCESSABLE_ENTITY, Json(ErrorBody { error })).into_response(); } - if let Err(error) = process_due_ticks( - &state, - &runtime, - ghostlight_dungeon::domain::TickSource::ReturnCatchUp, - false, - ) - .await + if player_command_requires_return_catch_up(&command) + && let Err(error) = process_due_ticks( + &state, + &runtime, + ghostlight_dungeon::domain::TickSource::ReturnCatchUp, + false, + ) + .await { return ( StatusCode::CONFLICT, @@ -4722,6 +4723,13 @@ fn player_http_command_allowed(command: &WorldCommand, player_actor_id: &str) -> } } +fn player_command_requires_return_catch_up(command: &WorldCommand) -> bool { + matches!( + command, + WorldCommand::Speak { .. } | WorldCommand::Attempt { .. } | WorldCommand::Wait { .. } + ) +} + fn validate_player_http_command(command: &WorldCommand) -> Result<(), String> { let bounded = |label: &str, value: &str, max: usize| { if value.trim().is_empty() || value.chars().count() > max { @@ -6018,6 +6026,48 @@ mod tests { )); } + #[test] + fn only_fictional_player_commands_require_return_catch_up() { + assert!(player_command_requires_return_catch_up( + &WorldCommand::Speak { + expected_revision: 4, + actor_id: "player".into(), + text: "Hello.".into(), + intended_effect: None, + } + )); + assert!(player_command_requires_return_catch_up( + &WorldCommand::Attempt { + actor_id: "player".into(), + assessment_digest: "sha256:assessment".into(), + } + )); + assert!(player_command_requires_return_catch_up( + &WorldCommand::Wait { + expected_revision: 4, + minutes: 10, + } + )); + assert!(!player_command_requires_return_catch_up( + &WorldCommand::Assess { + expected_revision: 4, + intent: ActionIntent { + actor_id: "player".into(), + description: "Inspect the seal.".into(), + intended_effect: "Learn whether it has authority here.".into(), + }, + proposal: None, + } + )); + assert!(!player_command_requires_return_catch_up( + &WorldCommand::SetResolutionBudget { + expected_revision: 4, + expected_resolution_epoch: 2, + active_cell_budget: 8, + } + )); + } + #[test] fn player_http_command_admission_bounds_cost_and_fictional_time() { assert!( diff --git a/docs/architecture/ghostlight-dungeon-mvp.md b/docs/architecture/ghostlight-dungeon-mvp.md index 1b492d9..c102d12 100644 --- a/docs/architecture/ghostlight-dungeon-mvp.md +++ b/docs/architecture/ghostlight-dungeon-mvp.md @@ -200,8 +200,9 @@ roll's selected band in the same atomic commit as the roll receipt. Narrative stakes describe that transition; they are no longer the transition. The scheduler may calculate pending work, but `AdvanceStrategicTick` owns the -transition. Return catch-up invokes that same command before the next player -action is admitted. +transition. Return catch-up invokes that same command before the next fictional +player action is admitted. Private assessment and resolution-policy edits do +not enter catch-up because neither advances or enters the fiction. ### Narration projection authority @@ -678,7 +679,9 @@ notification that drops the in-flight background wave future, aborting its cell tasks and provider requests before further stages launch. Live requests hold a shared commit gate; scheduler commits require the exclusive side and therefore cannot cross a live request. Return catch-up deliberately uses the live command -path and completes required ticks before admitting the player's next action. +path and completes required ticks before admitting the player's next fictional +action. `Assess` and resolution-policy changes remain outside fictional time and +therefore do not launch or wait for strategic simulation. ### Eve/CultMesh and browser diff --git a/notes/ghostlight-current-system-map.md b/notes/ghostlight-current-system-map.md index bca342e..e2019db 100644 --- a/notes/ghostlight-current-system-map.md +++ b/notes/ghostlight-current-system-map.md @@ -135,13 +135,15 @@ exact campaign revision The model owns no tick mutation. A provider failure or invalid proposal leaves the campaign revision and world time untouched. Background inference checks live-turn pressure before launch and again before commit; return catch-up uses -the same command path with player-turn priority. +the same command path with player-turn priority for fictional commands. A live request also interrupts an in-flight scheduler wave. Dropping that wave aborts its parallel cell tasks before they can launch later Persona stages, and a shared/exclusive commit gate makes scheduler commit impossible while any live request is active. Return catch-up is intentionally exempt because it is part of -the live request and must finish before the requested player action. +the live request and must finish before the requested fictional action. Private +assessment and resolution-policy edits neither advance fiction nor invoke +catch-up. Resolution-demand focal IDs are salience hints, not partition commands. They cannot create mandatory singleton cells or exceed the configured budget. Cell