Keep private assessment outside strategic catch-up

This commit is contained in:
Meta Vens 2026-08-23 01:07:58 +02:00
commit e5ef5e22ea
3 changed files with 67 additions and 12 deletions

View file

@ -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!(

View file

@ -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

View file

@ -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