From bee3c440bfff02c16cb5a5305346d4440c8ca88f Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 23 Aug 2026 17:58:37 +0200 Subject: [PATCH] fix(game): restore effect-seven multiball spawn Use the exact record 148 spawn point and the reconstructed 0.8 * 3800 millipixel velocity for ball slot two. This removes the guessed shooter-lane launch and keeps the multiball lifecycle tied to the recovered effect-seven path. Test Plan: - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - rumdl check CHANGELOG.md RECONSTRUCTION.md README.md - git diff --check --- tdkpin-rs/CHANGELOG.md | 3 +++ tdkpin-rs/RECONSTRUCTION.md | 2 +- tdkpin-rs/src/game.rs | 13 ++++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tdkpin-rs/CHANGELOG.md b/tdkpin-rs/CHANGELOG.md index d17adbf..93a9bf1 100644 --- a/tdkpin-rs/CHANGELOG.md +++ b/tdkpin-rs/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to ### Fixed +- Spawn effect-seven ball 2 from record 148 at `(17,23)` with exact + `(0,3040)` millipixel velocity, instead of launching a guessed second ball + upward from the shooter lane. - Replace invented bumper outline/cooldown effects with the original per-record 5/20/10 callback countdowns and exact overlay-A render rectangles for bumpers 51-53 and targets 140-147/150-152. diff --git a/tdkpin-rs/RECONSTRUCTION.md b/tdkpin-rs/RECONSTRUCTION.md index f473dcd..82b0baf 100644 --- a/tdkpin-rs/RECONSTRUCTION.md +++ b/tdkpin-rs/RECONSTRUCTION.md @@ -24,7 +24,7 @@ implementation. | 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. 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. 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. | | Ball launcher and nudge input | Recovered | The initial 32-bit fixed-point coordinates decode to `(325, 413)` in the right shooter lane. Each Down keydown subtracts `15*50 = 750` millipixels, release subtracts another `15*100 = 1500`, and the result follows the recovered randomized `-3800` lower and `-2280` weak upper clamp branches. The ten decoration frames use the same strict 750-millipixel thresholds. Left Shift and keypad 3 apply their directional `(50-Random(20))*15` impulses; Space uses the recovered Real48 horizontal factor and `(60-Random(20))*15` vertical impulse. Each nudge adds 25 to the wrapping 16-bit tilt counter, compares it with `30+Random(10)`, and the detail timer decrements a nonzero counter once per callback. | -| Physics arithmetic | Recovered Real48 core | Production movement uses the original 10 ms millipixel substep, `+15` vertical acceleration, Real48 `3800/speed` clamp, type-2 distance/cross gates, type-1 midpoint normal, surface-distance candidate ordering, persistent Real48 spin, and the common impulse response. Dynamic records 174/175 transfer normal impulse to the other ball before applying `normal_velocity-1000` to the moving ball. The original Borland seed update, high-word `Random(n)`, and normalized Real48 random register result drive launcher variation, effects, claw terminals, magnetic fields, and trigger response. Type-4 triggers retain a separate transient entry flag; type-3 captures retain per-player 16-bit contact words, deep-inside pull/hold progression to age 300, first-contact sound, and `99`/`2` completion sentinels. Zero-spin C harnesses and retained-spin Wine probes are tested separately rather than conflated. | +| Physics arithmetic | Recovered Real48 core | Production movement uses the original 10 ms millipixel substep, `+15` vertical acceleration, Real48 `3800/speed` clamp, type-2 distance/cross gates, type-1 midpoint normal, surface-distance candidate ordering, persistent Real48 spin, and the common impulse response. Dynamic records 174/175 transfer normal impulse to the other ball before applying `normal_velocity-1000` to the moving ball; effect seven spawns record/slot 2 from record 148 coordinates `(17000,23000)` with exact `(0,3040)` velocity. The original Borland seed update, high-word `Random(n)`, and normalized Real48 random register result drive launcher variation, effects, claw terminals, magnetic fields, and trigger response. Type-4 triggers retain a separate transient entry flag; type-3 captures retain per-player 16-bit contact words, deep-inside pull/hold progression to age 300, first-contact sound, and `99`/`2` completion sentinels. Zero-spin C harnesses and retained-spin Wine probes are tested separately rather than conflated. | | Rules | Recovered gameplay paths | Player count, controls, the five three-line bumper-value groups, four three-line TDK-diamond groups, five doubling-value lock holes, wheel-reset target, seven-way effect selector/consumer including multiball, permanent double scoring, and four exact media/extra-ball thresholds follow original help/code paths, globals, and object flags. The ninth diamond pays the original 24,464 completion value; the following completed bank enables double scoring, and later completions add 100,000 to the per-player secondary score. Bumpers 51-53 and targets 140-147/150-152 use the original 5/20/10 active-callback countdowns and exact overlay-A render rectangles. Record 121 runs the six-callback DAT600 target rotation with exact 91x90 frames/target points and rotates the five contact/item values at state 6. Completing all five lock holes suspends physics for the full 281-callback panel animation, clears contact/item state at the recovered boundaries, and uses the exact WAVE 2013/2012/stop sequence. Turn changes mirror the original save/load of all 175 collision record states: wheel/top targets, active/contact slots, selected effect, and multiball readiness remain attached to their player. Claw contact and all initially active type-4 targets use recovered records. The top three targets score 500 each and independently enable the left, center, or right magnetic field record; each field pulls the ball upward until it exits and then deactivates. The claw state machine and release table have live differential coverage for all four random terminals. | | 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. 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, and each add operation can advance at most one media threshold. | | 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 name screen before the table is shown and play resumes. Persisted updates use portable JSON rather than rewriting the Win16 file. | diff --git a/tdkpin-rs/src/game.rs b/tdkpin-rs/src/game.rs index c2ac7e4..9eff76b 100644 --- a/tdkpin-rs/src/game.rs +++ b/tdkpin-rs/src/game.rs @@ -1155,8 +1155,8 @@ impl Game { } 7 => { self.secondary_ball = Some(Ball { - position: LAUNCHER_POSITION, - velocity: vec2(0.0, -300.0), + position: vec2(17.0, 23.0), + velocity: MilliVec { x: 0, y: 3_040 }.to_velocity_per_second(), in_launcher: false, spin: Real48::ZERO, }); @@ -1844,14 +1844,17 @@ mod tests { let spawned = game .secondary_ball .expect("effect seven should spawn a second ball"); - assert_eq!(spawned.position, LAUNCHER_POSITION); - assert_eq!(spawned.velocity, vec2(0.0, -300.0)); + assert_eq!(spawned.position, vec2(17.0, 23.0)); + assert_eq!( + MilliVec::from_velocity_per_second(spawned.velocity), + MilliVec { x: 0, y: 3_040 } + ); game.advance_secondary_ball(&mut events); let moving_ball = game .secondary_ball .expect("second ball should remain active"); - assert!(moving_ball.position.y < spawned.position.y); + assert!(moving_ball.position.y > spawned.position.y); let balls_before = game.player().balls; game.drain(&mut events);