diff --git a/tdkpin-rs/CHANGELOG.md b/tdkpin-rs/CHANGELOG.md index 8221ef3..2ea2003 100644 --- a/tdkpin-rs/CHANGELOG.md +++ b/tdkpin-rs/CHANGELOG.md @@ -175,6 +175,9 @@ and this project adheres to and a player's final ball now clear the key latch, release a raised flipper through its normal WAVE 2021 timer edge, and require physical key-up before the same key can raise it again. +- Match the original low-byte keyboard scans: both Ctrl keys operate the left + flipper, both Enter keys operate the right, and the physical `0x1b` + main-keyboard plus/right-bracket position starts or adds players. - Route a single-ball type-3 completion during Tilt through `1000:ae6e` instead of granting a free launcher reset: lock holes consume the ball, while record 148 can still take its required-state special-respawn branch. diff --git a/tdkpin-rs/README.md b/tdkpin-rs/README.md index 2220638..76fa01c 100644 --- a/tdkpin-rs/README.md +++ b/tdkpin-rs/README.md @@ -54,10 +54,10 @@ for long end-to-end validation runs. | Action | Original key | Additional modern key | | --- | --- | --- | -| Start game / add up to 4 players | `+` | `=` uses the same main-keyboard key | +| Start game / add up to 4 players | Keypad `+` or the main `+`/`]` position | `=` is also accepted | | Charge launcher | Hold Down arrow, release to launch | - | -| Left flipper | Left Ctrl | `A` or Left arrow | -| Right flipper | Keypad Enter | Right Ctrl, `D`, or Right arrow | +| Left flipper | Ctrl (left or right) | `A` or Left arrow | +| Right flipper | Enter (main or keypad) | `D` or Right arrow | | Nudge | Space, Left Shift, keypad `3` | Right Shift aliases keypad `3` | | Help | F1 | Enter/Escape returns | | Settings | `TDKPIN.INI` before launch | F10 opens the portable settings screen | diff --git a/tdkpin-rs/RECONSTRUCTION.md b/tdkpin-rs/RECONSTRUCTION.md index 1e1c0b5..8b0dfb7 100644 --- a/tdkpin-rs/RECONSTRUCTION.md +++ b/tdkpin-rs/RECONSTRUCTION.md @@ -29,7 +29,7 @@ implementation. | Numeric scoring | Recovered gameplay values | Static scores come from the initialized 175-object ledger. Dynamic bumper progression, target-bank completion, diamond awards, 10k-160k lock bonuses, 310k transfer, six effect values, multiball mode, and all four media thresholds are transcribed from `1000:b476`, `1000:c4e1`, `1000:bc36`, and live state probes. The central helper suppresses all mutation during Tilt, applies a 2x active-multiball factor, and then independently applies the active ball's 2x factor, allowing the original 4x stack. Lock and effect awards share the original per-player secondary score and display multiplier; the fifth hole transfers and clears it, increments the multiplier, and grants the recovered ball award. Score mutation uses the original 32-bit wrapping behavior; media markers use the binary's signed-high/unsigned-low comparison, and each add operation can advance at most one threshold. Type-4 WAVE 2004, bumper WAVE 2006, and bank-completion WAVE 2017 dispatch before their associated score mutations, leaving a crossed marker's WAVE 2007 as the audible asynchronous sample. Rendering separately follows `1008:0996`'s eight-place 130x40 fields/right edge X=606 and `1008:0b9e`'s strict signed status thresholds; configured-player rows and 5x5 remaining-ball markers retain their exact dimensions and current/noncurrent counts. | | High scores | Recovered visible flow; portable storage | The original 276-byte table is decoded as ten `IWIK`-XOR-obfuscated little-endian scores plus ten 22-byte names. Each player is checked immediately when their own last ball is lost; qualifying scores use the original signed-high/unsigned-low comparison and a `TDK Pinball Player`-prefilled DIALOG 32514 flow with the exact caption, prompt, 21-character limit, OK/Cancel semantics, and allowance for an empty accepted name. The `HighScore` child uses exact DAT993 at 360x300, heading `(180,27)`, name/rank X=30, right-aligned score X=320, and ten 20-pixel rows before play resumes. Persisted updates use portable JSON rather than rewriting the Win16 file. | | Configuration | Recovered behavior; portable follow-up storage | Before portable JSON exists, Rust reads the original case-insensitive `[Settings]` keys `Language`, `Speed`, and singular `Sound` from the executable-adjacent INI, falling back to the embedded distributed TDKPIN.INI. Values retain the Win16 defaults/clamps; the shipped file therefore selects German, speed 3, and sound-on because its plural `Sounds` line does not override singular `Sound`. F12 toggles only runtime playback without UI or persistence. Later explicit F10 changes use the platform user-data directory. | -| Windows UI shell | Original gameplay surface with native host shell | Win16 window ownership and GDI calls are replaced by a fixed native window, but the visible game, help resources, automatic per-player high-score flow, original keys, and 640x460 logical pixels are retained. Idle mode advances on the selected detail callback and reproduces `idle_transition_tick`: target/word-quad/record-strip chases, magnetic flashes, four-point chase, DAT801-809 item progression, and the circular BITMAP500 `WELCOME TO THE MACHINE` marquee composited over exact DAT997 regions. F2/F3 remain unassigned as in normal original operation; optional portable settings and table viewers use F10/F9 and do not replace gameplay input. | +| Windows UI shell | Original gameplay surface with native host shell | Win16 window ownership and GDI calls are replaced by a fixed native window, but the visible game, help resources, automatic per-player high-score flow, original keys, and 640x460 logical pixels are retained. Idle mode advances on the selected detail callback and reproduces `idle_transition_tick`: target/word-quad/record-strip chases, magnetic flashes, four-point chase, DAT801-809 item progression, and the circular BITMAP500 `WELCOME TO THE MACHINE` marquee composited over exact DAT997 regions. The low-byte scan aliases are preserved: both Ctrl keys map left, both Enter keys map right, and keypad plus or the physical main-keyboard `0x1b` plus/right-bracket position adds players. F2/F3 remain unassigned as in normal original operation; optional portable settings and table viewers use F10/F9 and do not replace gameplay input. | ## Extracted asset inventory diff --git a/tdkpin-rs/src/app.rs b/tdkpin-rs/src/app.rs index fd8c9b4..c281864 100644 --- a/tdkpin-rs/src/app.rs +++ b/tdkpin-rs/src/app.rs @@ -11,6 +11,7 @@ use std::path::Path; const WIDTH: f32 = 640.0; const HEIGHT: f32 = 460.0; const DETAIL_TIMER_SECONDS: [f32; 5] = [0.050, 0.040, 0.030, 0.020, 0.010]; +const ADD_PLAYER_KEYS: [KeyCode; 3] = [KeyCode::KpAdd, KeyCode::RightBracket, KeyCode::Equal]; const TARGET_POSITIONS: [[(f32, f32); 5]; 6] = [ [(9.0, 41.0), (23.0, 11.0), (56.0, 14.0), (63.0, 47.0), (35.0, 63.0)], [(11.0, 45.0), (18.0, 13.0), (52.0, 11.0), (63.0, 42.0), (39.0, 64.0)], @@ -45,6 +46,10 @@ fn bumper_value_indicator_count(value: u32) -> usize { .min(BUMPER_VALUE_REGIONS.len()) } +fn add_player_pressed() -> bool { + ADD_PLAYER_KEYS.into_iter().any(is_key_pressed) +} + fn visible_score_digits(mut value: u32) -> Vec<(u8, u8)> { let mut digits = Vec::with_capacity(8); for position in 0..8 { @@ -320,7 +325,7 @@ impl App { fn update_attract(&mut self) { self.attract .update(get_frame_time(), self.saved.settings.speed); - if is_key_pressed(KeyCode::KpAdd) || is_key_pressed(KeyCode::Equal) { + if add_player_pressed() { self.game = Some(Game::new(1)); self.screen = Screen::Playing; self.assets.play(2001, self.sounds_enabled); @@ -328,7 +333,7 @@ impl App { } fn update_game(&mut self) { - if (is_key_pressed(KeyCode::KpAdd) || is_key_pressed(KeyCode::Equal)) + if add_player_pressed() && self .game .as_mut() @@ -337,10 +342,11 @@ impl App { self.assets.play(2001, self.sounds_enabled); } let left_flipper = is_key_down(KeyCode::LeftControl) + || is_key_down(KeyCode::RightControl) || is_key_down(KeyCode::A) || is_key_down(KeyCode::Left); let right_flipper = is_key_down(KeyCode::KpEnter) - || is_key_down(KeyCode::RightControl) + || is_key_down(KeyCode::Enter) || is_key_down(KeyCode::D) || is_key_down(KeyCode::Right); let nudge = if is_key_pressed(KeyCode::LeftShift) { @@ -1360,6 +1366,10 @@ mod tests { assert_eq!(bumper_value_indicator_count(2_000), 1); assert_eq!(bumper_value_indicator_count(6_000), 5); assert_eq!(bumper_value_indicator_count(u32::MAX), 5); + assert_eq!( + ADD_PLAYER_KEYS, + [KeyCode::KpAdd, KeyCode::RightBracket, KeyCode::Equal] + ); } #[test]