From d8cb7591b517653452d7a19724eeae06ae60e4ba Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 23 Aug 2026 20:04:34 +0200 Subject: [PATCH] fix(audio): suppress tilted drain cue before reset normal_ball_end asks the sound helper for WAVE 2008 before reset_ball_state clears Tilt. The helper therefore suppresses the drain cue for a tilted ball. Rust queued WAVE 2008 unconditionally and only cleared Tilt afterward. Emit the drain cue only when the pre-reset state is not tilted. Keep later timer epilogue sounds unchanged, so clearing Tilt can still allow a latched flipper's normal WAVE 2021 release edge. Test Plan: - `cargo test --workspace --all-targets --all-features` -- 119 passed - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed - `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed - `git diff --cached --check` -- passed --- tdkpin-rs/CHANGELOG.md | 3 ++- tdkpin-rs/RECONSTRUCTION.md | 2 +- tdkpin-rs/src/game.rs | 7 +++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tdkpin-rs/CHANGELOG.md b/tdkpin-rs/CHANGELOG.md index d9d9ce5..35f37c1 100644 --- a/tdkpin-rs/CHANGELOG.md +++ b/tdkpin-rs/CHANGELOG.md @@ -161,7 +161,8 @@ and this project adheres to playback and restores bank, trigger, capture, nudge, tilt, and drain cues. Type-4 WAVE 2004 and bumper WAVE 2006 now precede their score mutations so a simultaneously crossed media marker leaves WAVE 2007 audible, as in the - monophonic original. + monophonic original. A tilted drain suppresses WAVE 2008 before reset, while + a following flipper-release edge may still play WAVE 2021 after reset. - Advance media/extra-ball thresholds only from score additions, preserve 32-bit score wrapping, and restore the ninth-diamond ordering: its 24,464 award remains single, then ball-scoped double scoring and all three magnetic diff --git a/tdkpin-rs/RECONSTRUCTION.md b/tdkpin-rs/RECONSTRUCTION.md index 2103e9f..6ec4771 100644 --- a/tdkpin-rs/RECONSTRUCTION.md +++ b/tdkpin-rs/RECONSTRUCTION.md @@ -20,7 +20,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, help, ball, wheel, robot, plunger, media, and item frames. DAT400-407 expose the current per-player bonus/collect/release effect at `(55,79)` while preserving the saved 14x14 wheel overlap. 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. Like Win16 `SndPlaySound(SND_ASYNC|SND_NODEFAULT)`, each new resource stops the previous one and plays at the host mixer level without an application-side attenuation. WAV 2022 is loaded by the original generic resource loop but has no playback call and is therefore never played by Rust. | +| 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, nudge-then-tilt ordering, and Tilt suppression of the pre-reset WAVE-2008 drain cue. Like Win16 `SndPlaySound(SND_ASYNC|SND_NODEFAULT)`, each new resource stops the previous one and plays at the host mixer level without an application-side attenuation. 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. | | 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. With two balls, the key path still consumes those active-window draws, then applies its separately recovered fixed/shared random slot impulse to both saved velocities. 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. Flipper key flags and raised geometry survive ordinary drains; Tilt and a player's final ball clear the flags, lower raised flippers through the normal WAVE-2021 timer edge, and require a physical key release before another press. | diff --git a/tdkpin-rs/src/game.rs b/tdkpin-rs/src/game.rs index 68fec8b..0109c01 100644 --- a/tdkpin-rs/src/game.rs +++ b/tdkpin-rs/src/game.rs @@ -2088,7 +2088,9 @@ impl Game { (player.balls == 0 && player.extra_balls == 0).then_some(player.score); self.save_current_rule_state(); events.push(Event::Drain); - events.push(Event::Sound(2008)); + if !self.tilted { + events.push(Event::Sound(2008)); + } if let Some(score) = high_score_candidate { events.push(Event::HighScoreCandidate(score)); self.flipper_release_latch[0] |= self.flipper_inputs.left_raised; @@ -4198,10 +4200,11 @@ mod tests { left_flipper: true, ..Controls::default() }; - game.update(0.030, 3, held); + let events = game.update(0.030, 3, held); assert!(!game.tilted); assert!(game.ball.in_launcher); + assert!(!events.contains(&Event::Sound(2008))); assert!(game.flipper_release_latch[0]); game.update(0.0, 3, held); assert!(!game.flipper_inputs.left_raised);