feat(runtime): apply mission script outcomes
This commit is contained in:
@@ -298,6 +298,31 @@ pub struct MissionScriptVarSetState {
|
|||||||
pub values: Vec<VarSetDefault>,
|
pub values: Vec<VarSetDefault>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recovered mission outcome controlled by the `Iron3D` script host callback.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub enum MissionScriptOutcome {
|
||||||
|
/// No recovered callback has ended the mission.
|
||||||
|
#[default]
|
||||||
|
Active,
|
||||||
|
/// `Handler(30)` reached the proven mission-complete callback branch.
|
||||||
|
Completed,
|
||||||
|
/// `Handler(30)` reached the proven mission-fail callback branch.
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of applying one recovered script host callback command.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum MissionScriptHostEffect {
|
||||||
|
/// The callback did not match a currently recovered host branch.
|
||||||
|
Unhandled,
|
||||||
|
/// A previously active or failed mission became completed.
|
||||||
|
MissionCompleted,
|
||||||
|
/// A previously active or completed mission became failed.
|
||||||
|
MissionFailed,
|
||||||
|
/// The recovered target state was already current, so `Iron3D` has no state transition.
|
||||||
|
NoChange,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
struct MissionLoadOptions {
|
struct MissionLoadOptions {
|
||||||
fail_after_registered_objects: Option<usize>,
|
fail_after_registered_objects: Option<usize>,
|
||||||
@@ -443,6 +468,7 @@ struct LoadedMissionState {
|
|||||||
script_varset: Option<MissionScriptVarSet>,
|
script_varset: Option<MissionScriptVarSet>,
|
||||||
script_init_states: Vec<MissionScriptInitState>,
|
script_init_states: Vec<MissionScriptInitState>,
|
||||||
script_varset_states: Vec<MissionScriptVarSetState>,
|
script_varset_states: Vec<MissionScriptVarSetState>,
|
||||||
|
script_outcome: MissionScriptOutcome,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Engine error.
|
/// Engine error.
|
||||||
@@ -1146,6 +1172,7 @@ fn load_mission_with_options_and_progress(
|
|||||||
script_varset: loaded_scripts.varset,
|
script_varset: loaded_scripts.varset,
|
||||||
script_init_states: loaded_scripts.init_states,
|
script_init_states: loaded_scripts.init_states,
|
||||||
script_varset_states: loaded_scripts.varset_states,
|
script_varset_states: loaded_scripts.varset_states,
|
||||||
|
script_outcome: MissionScriptOutcome::Active,
|
||||||
});
|
});
|
||||||
Ok((summary, trace))
|
Ok((summary, trace))
|
||||||
}
|
}
|
||||||
@@ -1306,6 +1333,62 @@ pub fn resolve_loaded_handler30(
|
|||||||
resolve_handler30_from_varset_state(declarations, values, instruction)
|
resolve_handler30_from_varset_state(declarations, values, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the currently recovered mission outcome, if a mission is loaded.
|
||||||
|
#[must_use]
|
||||||
|
pub fn loaded_mission_script_outcome(engine: &Engine) -> Option<MissionScriptOutcome> {
|
||||||
|
engine.loaded.as_ref().map(|state| state.script_outcome)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies the recovered `Iron3D` host-callback mission branch.
|
||||||
|
///
|
||||||
|
/// Only `(mode=0, command=0, payload=0|1)` has a proven independent meaning:
|
||||||
|
/// payload zero marks failure and payload one marks completion. Other callback
|
||||||
|
/// commands are retained as [`MissionScriptHostEffect::Unhandled`] until their
|
||||||
|
/// `Iron3D` consumer branches are recovered.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if no mission is loaded.
|
||||||
|
pub fn apply_loaded_script_host_callback(
|
||||||
|
engine: &mut Engine,
|
||||||
|
command: VmHostCallbackCommand,
|
||||||
|
) -> Result<MissionScriptHostEffect, EngineError> {
|
||||||
|
let state = engine
|
||||||
|
.loaded
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| EngineError::ScriptRuntime {
|
||||||
|
clan_index: 0,
|
||||||
|
message: "mission script host state is unavailable".to_string(),
|
||||||
|
})?;
|
||||||
|
Ok(apply_recovered_script_host_callback(
|
||||||
|
&mut state.script_outcome,
|
||||||
|
command,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_recovered_script_host_callback(
|
||||||
|
outcome: &mut MissionScriptOutcome,
|
||||||
|
command: VmHostCallbackCommand,
|
||||||
|
) -> MissionScriptHostEffect {
|
||||||
|
if command.mode != 0 || command.first != 0 {
|
||||||
|
return MissionScriptHostEffect::Unhandled;
|
||||||
|
}
|
||||||
|
let next = match command.second {
|
||||||
|
0 => MissionScriptOutcome::Failed,
|
||||||
|
1 => MissionScriptOutcome::Completed,
|
||||||
|
_ => return MissionScriptHostEffect::Unhandled,
|
||||||
|
};
|
||||||
|
if *outcome == next {
|
||||||
|
return MissionScriptHostEffect::NoChange;
|
||||||
|
}
|
||||||
|
*outcome = next;
|
||||||
|
match next {
|
||||||
|
MissionScriptOutcome::Completed => MissionScriptHostEffect::MissionCompleted,
|
||||||
|
MissionScriptOutcome::Failed => MissionScriptHostEffect::MissionFailed,
|
||||||
|
MissionScriptOutcome::Active => MissionScriptHostEffect::Unhandled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_handler30_from_varset_state(
|
fn resolve_handler30_from_varset_state(
|
||||||
declarations: &MissionScriptVarSet,
|
declarations: &MissionScriptVarSet,
|
||||||
values: &MissionScriptVarSetState,
|
values: &MissionScriptVarSetState,
|
||||||
@@ -1862,6 +1945,57 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recovered_handler_thirty_host_callback_transitions_mission_outcome() {
|
||||||
|
let mut outcome = MissionScriptOutcome::Active;
|
||||||
|
assert_eq!(
|
||||||
|
apply_recovered_script_host_callback(
|
||||||
|
&mut outcome,
|
||||||
|
VmHostCallbackCommand {
|
||||||
|
mode: 0,
|
||||||
|
first: 0,
|
||||||
|
second: 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MissionScriptHostEffect::MissionFailed
|
||||||
|
);
|
||||||
|
assert_eq!(outcome, MissionScriptOutcome::Failed);
|
||||||
|
assert_eq!(
|
||||||
|
apply_recovered_script_host_callback(
|
||||||
|
&mut outcome,
|
||||||
|
VmHostCallbackCommand {
|
||||||
|
mode: 0,
|
||||||
|
first: 0,
|
||||||
|
second: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MissionScriptHostEffect::MissionCompleted
|
||||||
|
);
|
||||||
|
assert_eq!(outcome, MissionScriptOutcome::Completed);
|
||||||
|
assert_eq!(
|
||||||
|
apply_recovered_script_host_callback(
|
||||||
|
&mut outcome,
|
||||||
|
VmHostCallbackCommand {
|
||||||
|
mode: 0,
|
||||||
|
first: 0,
|
||||||
|
second: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MissionScriptHostEffect::NoChange
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
apply_recovered_script_host_callback(
|
||||||
|
&mut outcome,
|
||||||
|
VmHostCallbackCommand {
|
||||||
|
mode: 0,
|
||||||
|
first: 3,
|
||||||
|
second: 42,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MissionScriptHostEffect::Unhandled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn headless_scheduler_trace_skips_presentation_phases() {
|
fn headless_scheduler_trace_skips_presentation_phases() {
|
||||||
let mut engine = create(
|
let mut engine = create(
|
||||||
|
|||||||
@@ -252,6 +252,13 @@ call. Those branches are not yet assigned Rust gameplay meanings. Reproduce
|
|||||||
the current evidence with `capture-ai-init.ps1` and
|
the current evidence with `capture-ai-init.ps1` and
|
||||||
`ExportIron3dAiCallback.java`.
|
`ExportIron3dAiCallback.java`.
|
||||||
|
|
||||||
|
Runtime now applies only this recovered branch as
|
||||||
|
`apply_loaded_script_host_callback`: `(0, 0, 0)` transitions a loaded mission
|
||||||
|
to `Failed`, `(0, 0, 1)` transitions it to `Completed`, and a repeated target
|
||||||
|
state is a no-op just as the Iron3D guards require. The effect is deliberately
|
||||||
|
separate from audio/UI playback; commands 1, 3, 4, 5 and mode 2 return
|
||||||
|
`Unhandled` until their consumers are recovered.
|
||||||
|
|
||||||
### Handler(8): problem-record state write
|
### Handler(8): problem-record state write
|
||||||
|
|
||||||
`Handler(8)` is the ninth VM-table entry at GOG `ai.dll` VA `0x10009b0d`.
|
`Handler(8)` is the ninth VM-table entry at GOG `ai.dll` VA `0x10009b0d`.
|
||||||
|
|||||||
Reference in New Issue
Block a user