fix(ui): restore staged loading progress

Correct raw 1000:531e's Pascal-order BitBlt to destination (202,328), height 13, and width (stage-1)*7 from original DAT994. Render stages 2 through 35 over DAT995, add deterministic loading screenshots, and update the C harness and evidence ledger for the corrected 237x13 source.

Test Plan:
- bash original/tools/test_reconstructed_c.sh
- python3 original/tools/audit_reconstruction.py --require-complete
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- rumdl check original/C_RECONSTRUCTION_FINAL_AUDIT.md tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md tdkpin-rs/README.md
- cargo run --quiet -- --simulate loading --at 0.55 --screenshot /tmp/tdkpin-loading-mid.png
- git diff --check
This commit is contained in:
2026-08-23 19:03:09 +02:00
parent 8168d9dc23
commit c80f5e2136
11 changed files with 83 additions and 26 deletions
+4
View File
@@ -78,6 +78,10 @@ ledger gates below still pass. Raw `1000:c79c` also confirms that the response
reads fixed candidate slot 1 at `SS:...d8a2`; later nearer candidates are stored
in subsequent 0x34-byte slots but do not replace the applied record. A focused
two-candidate harness now seals that record-order behavior.
Raw `1000:531e` further corrects the Pascal-order BitBlt interpretation: the
DAT994 progress strip is drawn at `(202,328)` with height 13 and width
`(stage-1)*7`, rather than the impossible historical 328x202 read from a
237x13 bitmap. The focused loading-stage harness seals stages 1, 35, and 0.
## Reproducible gates
+1 -1
View File
@@ -6,7 +6,7 @@ address raw_name segment body_bytes status reconstructed_name module evidence no
1000:01d3 FUN_1000_01d3 Code1 155 verified tdkpin_main_window_construct reconstructed/tdkpin_game_windows.c disassembly 1000:01d3-026d; verified Borland constructor and TWindow base; USER GetSystemMetrics; reconstructed/tests/test_game_windows.c Exact 14-byte far-constructor frame, null base VMT, 640x480 geometry, signed divide-by-two screen centering, style 0x86ca0000 replacement, returned far object, and constructor-entry failure are represented and tested.
1000:0270 FUN_1000_0270 Code1 20357 verified tdkpin_setup_game_window reconstructed/tdkpin_game_setup.c disassembly 1000:0270-51f4; generated typed collision data checked against TDKPIN_GHIDRA_COMPLETE_RAW.c; verified setup dependencies; exact 20 quad calls; reconstructed/tests/test_game_setup.c; tools/generate_game_setup_collision_data.py --check Exact far-window RETF-4 ABI, low-memory message/close/RuntimeExit branch, ObjectWindows/cursor/palette setup, tick seed, all globals/receiver/player defaults, milli/pixel state, executable-relative INI-name construction, clamped language/speed configuration, timer creation/failure-without-close path, 177 ordered typed collision calls including preliminary 169/170 then final overwrite, twenty word quads, and 175 active/contact/zero mirrors are represented; success, low-memory, timer-failure, call-order, table-boundary, and mirror tests pass.
1000:51f5 FUN_1000_51f5 Code1 297 verified tdkpin_asset_window_destruct reconstructed/tdkpin_asset_cleanup.c disassembly 1000:51f5-531d; verified sound/message/TWindow/Borland dependencies; exact global/window bitmap inventory; reconstructed/tests/test_asset_cleanup.c Exact Wait cursor save/restore, sound unload, bd9/bd8 publication, optional +49 Dispose(mode 1), message drain, deletion order window+52 then 0859,0863,0865,085f,085b,0861,085d, arrays 430b[0..7],4319[1..4],4321[1..9], intentionally stale handles and retained window+54, PostQuitMessage(18), TWindow destructor, erase=true invalidation, derived ownership finish, six-byte cleanup, and 29-handle sequence are tested.
1000:531e FUN_1000_531e Code1 114 verified tdkpin_draw_loading_stage reconstructed/tdkpin_game_assets.c disassembly 1000:531e-538f; exact caller-BP/SS offsets; GDI compatible-DC/select/BitBlt/DeleteObject; reconstructed/tests/test_game_assets.c Exact BP-0c/-04/-02/+0e shared frame contract, byte-stage zero extension, 16-bit (stage-1)*7 wrap, fixed destination 13/y and 328x202 SRCCOPY, select/restore, intentional DeleteObject-on-HDC, four-byte cleanup, and scratch writes are branch-tested.
1000:531e FUN_1000_531e Code1 114 verified tdkpin_draw_loading_stage reconstructed/tdkpin_game_assets.c disassembly 1000:531e-538f; exact caller-BP/SS offsets; GDI compatible-DC/select/BitBlt/DeleteObject; reconstructed/tests/test_game_assets.c Exact BP-0c/-04/-02/+0e shared frame contract, byte-stage zero extension, 16-bit (stage-1)*7 width wrap, fixed destination (202,328), 13-pixel height, DAT994 source origin, SRCCOPY, select/restore, intentional DeleteObject-on-HDC, four-byte cleanup, and scratch writes are branch-tested.
1000:5390 FUN_1000_5390 Code1 1367 verified tdkpin_initialize_assets_and_redraw reconstructed/tdkpin_asset_initializer.c disassembly 1000:5390-58e6; all internal dependencies verified; exact DAT/BITMAP ID sequence and DGROUP/window offsets; reconstructed/tests/test_asset_initializer.c Exact public ten-byte ABI, entry palette select/unrealize/realize and message drain, static helper split at binary phases 53c5/5619, DAT IDs 995,994,998x2,400..407,701..704,801..809,900,997,901,600, BITMAP IDs 500/102/101, progress stages 2..35, sound init, handle publications, loading-surface draw/delete, palette/cursor restoration, already-redrawn palette retention, transient scene composition, optional mask/image ROPs with coordinate+19 dimensions, score factor/product and positive-player redraw, status sentinel/update, scene-ready publication, and all three control-flow phases are represented and tested.
1000:58e7 FUN_1000_58e7 Code1 111 verified tdkpin_palette_change_variant_a reconstructed/tdkpin_game_assets.c disassembly 1000:58e7-5955; exact ObjectWindowsMessage result offsets; USER IsIconic/InvalidateRect/UpdateWindow; reconstructed/tests/test_palette_messages.c Exact zero-wParam reentrancy reset/result zero, active suppression, iconic suppression, non-iconic invalidate/update ordering, result DWORD clear, state publication, uninitialized uint16 suppressed return, and eight-byte cleanup are branch-tested.
1000:5956 FUN_1000_5956 Code1 74 verified tdkpin_palette_change_variant_b reconstructed/tdkpin_game_assets.c disassembly 1000:5956-599f; shared palette-update state and invalidate helper; reconstructed/tests/test_palette_messages.c Exact wParam==0 and inactive conjunction, state set, null-rect/non-erasing invalidate, synchronous update, message result DWORD clear, no-op branches, and eight-byte cleanup are represented and tested.
1 address raw_name segment body_bytes status reconstructed_name module evidence notes
6 1000:01d3 FUN_1000_01d3 Code1 155 verified tdkpin_main_window_construct reconstructed/tdkpin_game_windows.c disassembly 1000:01d3-026d; verified Borland constructor and TWindow base; USER GetSystemMetrics; reconstructed/tests/test_game_windows.c Exact 14-byte far-constructor frame, null base VMT, 640x480 geometry, signed divide-by-two screen centering, style 0x86ca0000 replacement, returned far object, and constructor-entry failure are represented and tested.
7 1000:0270 FUN_1000_0270 Code1 20357 verified tdkpin_setup_game_window reconstructed/tdkpin_game_setup.c disassembly 1000:0270-51f4; generated typed collision data checked against TDKPIN_GHIDRA_COMPLETE_RAW.c; verified setup dependencies; exact 20 quad calls; reconstructed/tests/test_game_setup.c; tools/generate_game_setup_collision_data.py --check Exact far-window RETF-4 ABI, low-memory message/close/RuntimeExit branch, ObjectWindows/cursor/palette setup, tick seed, all globals/receiver/player defaults, milli/pixel state, executable-relative INI-name construction, clamped language/speed configuration, timer creation/failure-without-close path, 177 ordered typed collision calls including preliminary 169/170 then final overwrite, twenty word quads, and 175 active/contact/zero mirrors are represented; success, low-memory, timer-failure, call-order, table-boundary, and mirror tests pass.
8 1000:51f5 FUN_1000_51f5 Code1 297 verified tdkpin_asset_window_destruct reconstructed/tdkpin_asset_cleanup.c disassembly 1000:51f5-531d; verified sound/message/TWindow/Borland dependencies; exact global/window bitmap inventory; reconstructed/tests/test_asset_cleanup.c Exact Wait cursor save/restore, sound unload, bd9/bd8 publication, optional +49 Dispose(mode 1), message drain, deletion order window+52 then 0859,0863,0865,085f,085b,0861,085d, arrays 430b[0..7],4319[1..4],4321[1..9], intentionally stale handles and retained window+54, PostQuitMessage(18), TWindow destructor, erase=true invalidation, derived ownership finish, six-byte cleanup, and 29-handle sequence are tested.
9 1000:531e FUN_1000_531e Code1 114 verified tdkpin_draw_loading_stage reconstructed/tdkpin_game_assets.c disassembly 1000:531e-538f; exact caller-BP/SS offsets; GDI compatible-DC/select/BitBlt/DeleteObject; reconstructed/tests/test_game_assets.c Exact BP-0c/-04/-02/+0e shared frame contract, byte-stage zero extension, 16-bit (stage-1)*7 wrap, fixed destination 13/y and 328x202 SRCCOPY, select/restore, intentional DeleteObject-on-HDC, four-byte cleanup, and scratch writes are branch-tested. Exact BP-0c/-04/-02/+0e shared frame contract, byte-stage zero extension, 16-bit (stage-1)*7 width wrap, fixed destination (202,328), 13-pixel height, DAT994 source origin, SRCCOPY, select/restore, intentional DeleteObject-on-HDC, four-byte cleanup, and scratch writes are branch-tested.
10 1000:5390 FUN_1000_5390 Code1 1367 verified tdkpin_initialize_assets_and_redraw reconstructed/tdkpin_asset_initializer.c disassembly 1000:5390-58e6; all internal dependencies verified; exact DAT/BITMAP ID sequence and DGROUP/window offsets; reconstructed/tests/test_asset_initializer.c Exact public ten-byte ABI, entry palette select/unrealize/realize and message drain, static helper split at binary phases 53c5/5619, DAT IDs 995,994,998x2,400..407,701..704,801..809,900,997,901,600, BITMAP IDs 500/102/101, progress stages 2..35, sound init, handle publications, loading-surface draw/delete, palette/cursor restoration, already-redrawn palette retention, transient scene composition, optional mask/image ROPs with coordinate+19 dimensions, score factor/product and positive-player redraw, status sentinel/update, scene-ready publication, and all three control-flow phases are represented and tested.
11 1000:58e7 FUN_1000_58e7 Code1 111 verified tdkpin_palette_change_variant_a reconstructed/tdkpin_game_assets.c disassembly 1000:58e7-5955; exact ObjectWindowsMessage result offsets; USER IsIconic/InvalidateRect/UpdateWindow; reconstructed/tests/test_palette_messages.c Exact zero-wParam reentrancy reset/result zero, active suppression, iconic suppression, non-iconic invalidate/update ordering, result DWORD clear, state publication, uninitialized uint16 suppressed return, and eight-byte cleanup are branch-tested.
12 1000:5956 FUN_1000_5956 Code1 74 verified tdkpin_palette_change_variant_b reconstructed/tdkpin_game_assets.c disassembly 1000:5956-599f; shared palette-update state and invalidate helper; reconstructed/tests/test_palette_messages.c Exact wParam==0 and inactive conjunction, state set, null-rect/non-erasing invalidate, synchronous update, message result DWORD clear, no-op branches, and eight-byte cleanup are represented and tested.
+6 -6
View File
@@ -10,9 +10,9 @@ enum {
FRAME_PREVIOUS_BITMAP = -0x04,
FRAME_LOADING_BITMAP = -0x02,
FRAME_DESTINATION_DC = 0x0e,
LOADING_DESTINATION_X = 13,
LOADING_WIDTH = 328,
LOADING_HEIGHT = 202,
LOADING_DESTINATION_X = 202,
LOADING_DESTINATION_Y = 328,
LOADING_HEIGHT = 13,
WIN16_SRCCOPY = 0x00cc0020,
DGROUP_OVERLAY_BITMAP = 0x0867,
WINDOW_HANDLE_OFFSET = 4,
@@ -51,12 +51,12 @@ void tdkpin_draw_loading_stage(Win16FarPtr caller_bp, uint8_t stage)
HGDIOBJ16 previous = SelectObject16(memory_dc, loading_bitmap);
win16_write_u16(
frame_field(caller_bp, FRAME_PREVIOUS_BITMAP), 0, previous);
uint16_t wrapped_y = (uint16_t)(((uint16_t)stage - 1) * 7);
uint16_t wrapped_width = (uint16_t)(((uint16_t)stage - 1) * 7);
(void)BitBlt16(
destination,
LOADING_DESTINATION_X,
(INT16)wrapped_y,
LOADING_WIDTH,
LOADING_DESTINATION_Y,
(INT16)wrapped_width,
LOADING_HEIGHT,
memory_dc,
0,
@@ -5,7 +5,7 @@
static uint8_t g_stack[0x400];
static unsigned g_step;
static INT16 g_expected_y;
static INT16 g_expected_width;
HDC16 CreateCompatibleDC16(HDC16 dc)
{
@@ -37,8 +37,8 @@ BOOL16 BitBlt16(
{
assert(++g_step == 3);
assert(destination == 0x1111 && source == 0x2222);
assert(destination_x == 13 && destination_y == g_expected_y);
assert(width == 328 && height == 202);
assert(destination_x == 202 && destination_y == 328);
assert(width == g_expected_width && height == 13);
assert(source_x == 0 && source_y == 0);
assert(raster_operation == 0x00cc0020);
return 1;
@@ -50,14 +50,14 @@ BOOL16 DeleteObject16(HGDIOBJ16 object)
return 1;
}
static void run_stage(uint8_t stage, INT16 expected_y)
static void run_stage(uint8_t stage, INT16 expected_width)
{
memset(g_stack, 0, sizeof(g_stack));
Win16FarPtr frame = win16_make_far_pointer(0x6000, 0x100);
win16_write_u16(frame, 0x0e, 0x1111);
win16_write_u16(frame, 0xfffe, 0x3333);
g_step = 0;
g_expected_y = expected_y;
g_expected_width = expected_width;
tdkpin_draw_loading_stage(frame, stage);
assert(g_step == 5);
assert(win16_read_u16(win16_far_add_offset(frame, 0xfff4)) == 0x2222);
+3
View File
@@ -10,6 +10,9 @@ and this project adheres to
### Fixed
- Restore the staged loading presentation by blitting original DAT994 over
DAT995 at `(202,328)`, height 13 and clipped width `(stage-1)*7`; correct the
previously impossible Pascal-order C interpretation of a 328x202 blit.
- Restore the original idle/attract timer animation: target and word-quad
chases, magnetic flashes, five-record strip, four-point chase, DAT801-809
item progression, and the circular `WELCOME TO THE MACHINE` BITMAP500
+2 -2
View File
@@ -44,8 +44,8 @@ cargo run -- --simulate claw-6 --at 0.15 \
```
Use `--step N` instead of `--at SECONDS` to reproduce one exact update. The
available scenarios are `attract`, `autoplay`, `launcher`, `flippers`, `panel`,
`targets`, `claw-1`, `claw-6`, `claw-7`, and `claw-18`; `--seed N` fixes
available scenarios are `loading`, `attract`, `autoplay`, `launcher`, `flippers`,
`panel`, `targets`, `claw-1`, `claw-6`, `claw-7`, and `claw-18`; `--seed N` fixes
random choices.
`autoplay` charges each ball and operates the flippers from live ball position
for long end-to-end validation runs.
+1 -1
View File
@@ -19,7 +19,7 @@ implementation.
| Subsystem | Rust status | Evidence and boundary |
| --- | --- | --- |
| Artwork | Exact | All 34 custom DIB images, three standard bitmaps, icon, and palette derivatives are preserved in `assets/original/`. The game uses the original 640x460 table, loading, help, ball, wheel, robot, plunger, media, and diamond frames. |
| Artwork | Exact | All 34 custom DIB images, three standard bitmaps, icon, and palette derivatives are preserved in `assets/original/`. The game uses the original 640x460 table, help, ball, wheel, robot, plunger, media, and item frames. Loading presents DAT995 and advances the original DAT994 strip at raw `1000:531e` destination `(202,328)`, height 13, and clipped width `(stage-1)*7` through stages 2-35. |
| Audio | Exact samples and recovered dispatch | All 16 mono PCM WAV resources are embedded unchanged. Playback is emitted at the reconstructed call sites, including multi-sound bank completions and nudge-then-tilt ordering. WAV 2022 is loaded by the original generic resource loop but has no playback call and is therefore never played by Rust. |
| Help and languages | Exact | Original resource images 1001-1005 are displayed directly. |
| Playfield collision layout | Recovered | All 109 active type-2 line objects and 40 static active type-1 circles are transcribed from the original 175-object registration table. The registration routine converts its sideways inputs with `screen = (y, x - 20)` and accumulates explicitly relative objects. Every static record retains its `+0x49` layer mask; the scanner selects layer 1 below the old Y value 250,000 and layer 2 at or above it, while mask 3 records remain shared. Before detection, the raw `1000:9b69` predicted position must lie in the record bounds derived with the registered five-pixel ball margin. Type-2 records retain every recovered Real48 normal/tangent response pair and registered one-sided orientation. Type-1 records retain their swept-circle radius, radial rebound, tangent coupling, and bumper kick. Detection retains unresolved normal/material candidates so later scan-time motion changes can participate in the selected response. Raw stack slot `SS:...d8a2` makes the first detected record ID win; later nearer candidates are stored but never applied, and both C/Rust have two-candidate regression coverage for this quirk. Each flipper uses its exact two line records plus moving tip circle in both positions. Moving-flipper contact ports `1000:7ed9` rather than fitting live samples: delta-specific pivots/edges, integer cross gates, radial/penetration calculations, response-record gain, and position/velocity publication are tested against all four C harness directions and the raised release geometry. Object 174 is overwritten with the live first ball and Rust handles its ball-to-ball role directly. |
+45 -2
View File
@@ -113,6 +113,7 @@ pub struct App {
return_screen: Screen,
game: Option<Game>,
setting_row: usize,
loading_started: f64,
loading_until: f64,
last_help_click: f64,
message: String,
@@ -129,6 +130,7 @@ impl App {
let saved = persistence.load();
let render_target = render_target(640, 460);
render_target.texture.set_filter(FilterMode::Nearest);
let loading_started = get_time();
Self {
assets,
persistence,
@@ -138,7 +140,8 @@ impl App {
return_screen: Screen::Attract,
game: None,
setting_row: 0,
loading_until: get_time() + 1.1,
loading_started,
loading_until: loading_started + 1.1,
last_help_click: -1.0,
message: String::new(),
message_until: 0.0,
@@ -182,6 +185,14 @@ impl App {
}
}
pub fn set_simulation_loading(&mut self, steps: u64) {
self.game = None;
self.screen = Screen::Loading;
let step = u32::try_from(steps).unwrap_or(u32::MAX);
self.loading_started = get_time() - f64::from(step) / 120.0;
self.loading_until = get_time() + 60.0;
}
pub fn render_simulation(&self) {
self.draw_logical();
}
@@ -431,7 +442,7 @@ impl App {
clear_background(BLACK);
match self.screen {
Screen::Loading => draw_texture(&self.assets.loading, 0.0, 0.0, WHITE),
Screen::Loading => self.draw_loading(),
Screen::Help => {
let index = Language::ALL
.iter()
@@ -525,6 +536,28 @@ impl App {
self.draw_intro_marquee();
}
#[allow(clippy::cast_precision_loss)]
fn draw_loading(&self) {
draw_texture(&self.assets.loading, 0.0, 0.0, WHITE);
let stage = Self::loading_stage_at(get_time() - self.loading_started);
let width = i32::from(stage.saturating_sub(1)) * 7;
draw_texture_region(
&self.assets.loading_progress,
202.0,
328.0,
width.min(237) as f32,
13.0,
0.0,
0.0,
);
}
#[allow(clippy::cast_possible_truncation)]
fn loading_stage_at(elapsed: f64) -> u8 {
let progress = (elapsed / 1.1).clamp(0.0, 0.999_999);
u8::try_from(2 + (progress * 34.0).floor() as i32).unwrap_or(35)
}
fn draw_intro_marquee(&self) {
let Some(source_x) = self.attract.marquee_source_x() else {
return;
@@ -1211,6 +1244,16 @@ mod tests {
}
}
#[test]
fn loading_progress_covers_original_stages_two_through_thirty_five() {
assert_eq!(App::loading_stage_at(-1.0), 2);
assert_eq!(App::loading_stage_at(0.0), 2);
assert_eq!(App::loading_stage_at(1.1 / 34.0), 3);
assert_eq!(App::loading_stage_at(1.09), 35);
assert_eq!(App::loading_stage_at(99.0), 35);
assert_eq!(i32::from(App::loading_stage_at(99.0) - 1) * 7, 238);
}
#[test]
fn attract_phases_follow_the_reconstructed_tick_quotients() {
let at = |tick| AttractAnimation {
+3
View File
@@ -7,6 +7,7 @@ pub struct Assets {
pub active_table: Texture2D,
pub inactive_table: Texture2D,
pub loading: Texture2D,
pub loading_progress: Texture2D,
pub help: [Texture2D; 5],
pub media: [Texture2D; 4],
pub diamond: [Texture2D; 9],
@@ -29,6 +30,7 @@ impl Assets {
let active_table = texture(include_bytes!("../assets/original/images/dat_00997.png"));
let inactive_table = texture(include_bytes!("../assets/original/images/dat_00998.png"));
let loading = texture(include_bytes!("../assets/original/images/dat_00995.png"));
let loading_progress = texture(include_bytes!("../assets/original/images/dat_00994.png"));
let help = [
texture(include_bytes!("../assets/original/images/dat_01001.png")),
texture(include_bytes!("../assets/original/images/dat_01002.png")),
@@ -146,6 +148,7 @@ impl Assets {
active_table,
inactive_table,
loading,
loading_progress,
help,
media,
diamond,
+4 -4
View File
@@ -60,10 +60,10 @@ async fn run_simulation(request: Request) -> Result<(), String> {
}
if let Some(path) = &request.screenshot {
let mut app = App::load().await;
if request.scenario == Scenario::Attract {
app.set_simulation_attract(request.target_step);
} else {
app.set_simulation_game(simulation.game().clone());
match request.scenario {
Scenario::Loading => app.set_simulation_loading(request.target_step),
Scenario::Attract => app.set_simulation_attract(request.target_step),
_ => app.set_simulation_game(simulation.game().clone()),
}
app.render_simulation();
next_frame().await;
+9 -5
View File
@@ -13,6 +13,7 @@ const MAX_SIMULATION_STEPS: u64 = 72_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Scenario {
Loading,
Attract,
Autoplay,
Launcher,
@@ -28,6 +29,7 @@ pub enum Scenario {
impl Scenario {
pub const fn name(self) -> &'static str {
match self {
Self::Loading => "loading",
Self::Attract => "attract",
Self::Autoplay => "autoplay",
Self::Launcher => "launcher",
@@ -47,7 +49,8 @@ impl Scenario {
Self::Claw6 => Some(6),
Self::Claw7 => Some(7),
Self::Claw18 => Some(18),
Self::Attract
Self::Loading
| Self::Attract
| Self::Autoplay
| Self::Launcher
| Self::Flippers
@@ -62,6 +65,7 @@ impl FromStr for Scenario {
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"loading" => Ok(Self::Loading),
"attract" => Ok(Self::Attract),
"autoplay" => Ok(Self::Autoplay),
"launcher" => Ok(Self::Launcher),
@@ -73,7 +77,7 @@ impl FromStr for Scenario {
"claw-7" => Ok(Self::Claw7),
"claw-18" => Ok(Self::Claw18),
_ => Err(format!(
"unknown scenario {value:?}; expected attract, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, or claw-18"
"unknown scenario {value:?}; expected loading, attract, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, or claw-18"
)),
}
}
@@ -157,7 +161,7 @@ fn seconds_to_step(value: &str) -> Result<u64, String> {
}
pub const fn usage() -> &'static str {
"Usage:\n tdkpin-rs\n tdkpin-rs --simulate SCENARIO [--at SECONDS | --step N] [--screenshot FILE.png] [--trace FILE.json] [--seed N]\n\nScenarios: attract, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, claw-18"
"Usage:\n tdkpin-rs\n tdkpin-rs --simulate SCENARIO [--at SECONDS | --step N] [--screenshot FILE.png] [--trace FILE.json] [--seed N]\n\nScenarios: loading, attract, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, claw-18"
}
#[derive(Debug, Serialize, PartialEq)]
@@ -238,7 +242,7 @@ impl Simulation {
} else {
controls_for(self.scenario, self.step)
};
let events = if self.scenario == Scenario::Attract {
let events = if matches!(self.scenario, Scenario::Loading | Scenario::Attract) {
Vec::new()
} else {
self.game.update(SIMULATION_DT, 3, controls)
@@ -332,7 +336,7 @@ impl Simulation {
fn controls_for(scenario: Scenario, step: u64) -> Controls {
match scenario {
Scenario::Attract => Controls::default(),
Scenario::Loading | Scenario::Attract => Controls::default(),
Scenario::Autoplay => unreachable!("autoplay controls need mutable simulation state"),
Scenario::Launcher => Controls {
launch_down: step < u64::from(SIMULATION_HZ),