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
Normal ball-end handling keeps the original flipper key bytes and geometry
unless the current player has finished. Rust instead reset both flippers on
every drain, skipped the normal WAVE 2021 release edge, and treated a key still
held after Tilt as a fresh press once Tilt cleared.
Preserve flipper state across ordinary drains. Tilt and a player's final ball
now clear only the key flags, let the timer epilogue lower raised geometry, and
latch each physical key until key-up before accepting another press. This
matches the original WM_KEYDOWN/WM_KEYUP lifetime while retaining modern
per-frame input sampling.
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
The required-effect word at 1028:399a is record 148's +0x43 contact field.
The original special respawn clears that exact word before suppressing the
capture gate. Rust reset only its derived multiball state and left contact 2
behind, allowing a synthetic type-three rim candidate after respawn.
Clear record_contacts[148] with the derived state. Extend both armed and active
multiball respawn regressions to begin with the binary's contact sentinel and
prove that the transition removes it.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 118 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `rumdl check --flavor commonmark CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
The original timer advances DAT600 target rotation only from active_ball_tick.
A normal ball end does not clear its phase, so a pending rotation pauses while
the next ball waits in the launcher and resumes after launch. Rust advanced the
phase during launcher idle and then discarded it on drain.
Gate rotation updates with the active-ball state and stop clearing rotation,
panel, or claw mechanism state in the normal drain path where the binary leaves
them untouched. Seed the deterministic target scenario with an active ball so
it continues to exercise all six recovered phases.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 118 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
The binary stores the diamond-bank double flag at receiver +0x0bdc and clears
it in normal_ball_end. Rust stored that flag on Player, so it incorrectly
survived every normal drain and player turn. The special effect-seven respawn
is intentionally different because it bypasses normal_ball_end.
Represent the flag as an explicit ball-double state on Game. Clear it only on
the normal ball-end path, preserve it through the special respawn, and make the
first completed diamond bank after a normal drain re-enable double scoring
without adding 100,000 secondary points. Keep the independent multiball factor,
so both active factors still stack to 4x.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 117 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
Bumper flags are dispatched before the variable bumper award in the original
collision response. Rust duplicated the primary- and secondary-ball rule path
and appended WAVE 2006 after score mutation, which hid WAVE 2007 whenever that
award crossed a media/extra-ball threshold.
Share one bumper-rule helper for both ball slots, arm the five-callback visual
countdown, dispatch WAVE 2006, and only then add the weak or kicked bumper score.
An exact event-order regression keeps the threshold sound as the final audible
sample.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 116 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
The original score helper compares signed high words and unsigned low words,
which is a signed 32-bit comparison of the wrapped score bits. Rust used an
unsigned comparison and could award a media marker after the score crossed the
sign bit. Type-four trigger handling also queued WAVE 2004 after score mutation,
so it replaced WAVE 2007 when the same hit crossed an extra-ball threshold.
Use the binary's comparison semantics and dispatch the type-four hit sound
before both ordinary target and player-effect score mutations. Regression tests
cover both trigger paths, the exact monophonic event order, and a score with the
high bit set.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 115 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
The reconstructed collision response dispatches record flags before adding the
selected record's static score. Rust previously scored first and delayed the
permanent-double transition until a later completed bank, leaving the three
magnetic records inactive and producing the wrong ninth-bank total.
Apply flag-driven rules first, keep the 24,464 completion award at the old
single multiplier, then activate permanent double scoring and records 153, 6,
and 154 before the final 1,500-point collision score. Subsequent completed
banks now immediately add 100,000 to the per-player secondary score. Update the
regression test and reconstruction notes with the observable order.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 114 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `bash original/tools/test_reconstructed_c.sh` -- passed
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
- `cargo +nightly fmt -- --check` -- reports pre-existing whole-project diffs
Use the binary tangent velocity -(vx*normal_y + vy*normal_x)/length, then combine it with retained spin and the Real48 response coefficient. This single formula matches live outer/inner shooter walls, object 155, bumper 51, and the horizontal special-drain response; update C evidence and keep autoplay coverage on a target-reaching seed.
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
- original Wine traces: /tmp/original-outer-wall.csv /tmp/original-inner-wall.csv /tmp/original-circle155-after.csv /tmp/original-special-respawn.csv
- git diff --check
Correct the common projection to spin*0.4 + normal_velocity*response_tangent and seal line/circle behavior against current Wine one-substep traces. Preserve record-two's in-response special-respawn continuation, producing the live (16698,21191)/(-302,-1809) state after publishing (17000,23000)/(0,3040), and update the readable C evidence.
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
- live Wine record 2 trace: /tmp/original-special-respawn.csv
- live Wine object 155 trace: /tmp/original-circle155-after.csv
- git diff --check
Match records 174/175 by transferring normal impulse only when the other slot capture age is nonpositive. A held slot now keeps its velocity and the moving ball resolves with the full normal velocity; successful transfer retains the original normal_velocity-1000 response.
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
Track required effect-seven state through active multiball and implement 1000:ae6e's final single-ball respawn at record 148 without consuming a player marker. Preserve velocity, clear required state, and hold the 0875-style record-148 gate until a broadphase-active type-four record reenables it.
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
Finish a ball before record scanning when its clamped prediction leaves x=(0,340000] or y=(0,460000], and remove only the escaping slot during multiball. Delete the invented post-move y>470 shortcut so collision responses that land outside persist until the original next-substep check.
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
Remove the invented whole-frame nudge shake so nudges affect only recovered ball state, sound, and tilt behavior. Match monophonic SndPlaySound semantics by stopping the current WAV before each new resource and using full host mixer volume instead of an arbitrary 72 percent attenuation.
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
Load case-insensitive Language, Speed, and singular Sound values from an adjacent or embedded TDKPIN.INI before portable JSON exists, preserving Win16 defaults and clamps. Separate runtime playback from stored settings so F12 remains a silent, non-persistent toggle exactly like the original.
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
Carry predicted position separately through exact record-ID chunks, apply magnetic rewrites only at records 6/153/154, and honor type-three/dynamic broadphase. Match raw 1000:c79c by resolving fixed candidate slot one rather than a later nearer record, and restore related capture-age, tilt, and stacked multiball scoring side effects.
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 original/MECHANICS_PROGRESS.md tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md tdkpin-rs/README.md
- git diff --check
Interleave static ranges, type-three captures, type-four triggers, magnetic records, and dynamic ball records in exact ID order while tracking predicted position separately from mutable motion. Preserve type-three/dynamic broadphase and the raw stack quirk where fixed candidate slot one wins even when a later candidate is nearer; seal that behavior in both C and Rust harnesses.
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 original/MECHANICS_PROGRESS.md tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md tdkpin-rs/README.md
- git diff --check
Evaluate type-three captures from the pre-movement position, retain their missing radial rim candidates, and run type-four motion randomization before candidate resolution and position publication. Sensor groups now execute in binary ID order without Hold or Complete prematurely aborting later records.
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
Use unresolved type-one/type-two candidates in the production scan and gate detection on the original predicted-position bounds with the registered five-pixel ball margin. This restores the 1000:9b69 broadphase, including overshoot rejection, while keeping late response resolution ready for ordered type-three/type-four integration.
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
Split type-one and type-two detection from response resolution so the retained candidate can be applied to the motion state that exists after the complete record scan. Existing response functions remain compatibility wrappers, and a focused test proves late motion changes participate in resolution.
Test Plan:
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- git diff --check
Correct the readable 1000:9b69 reconstruction to read DGROUP 07db/07df and name the helper for its predicted-position role. Rust magnetic rectangles now enter only from an old position inside the record and deactivate when the recomputed prediction exits any edge, replacing the prior swept approximation.
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
- git diff --check
Remove the independent 30 ms tilt accumulator and decrement the wrapping 16-bit counter once at the end of each selected multimedia detail callback. This restores the original 50/40/30/20/10 ms decay cadence across all five settings.
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
Preserve the Win16 key path's two-stage multiball behavior: consume the active-window random formula, then apply one separately recovered slot impulse to both saved ball velocities. This restores the original RNG advancement and prevents ball one and ball two from receiving invented different nudges.
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
Carry the recovered +0x49 layer mask through every static line/circle and select the original upper/lower scan layer from the previous Y coordinate. Restore negative auxiliary thresholds and kick coefficients for bumpers and records 55, 74, and 107, including weak-hit scoring, rail sound, and tilt suppression.
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
Freeze the active ball count at each detail callback, integrate every substep for slot one before slot two, and stop a slot after its first collision or lifecycle action. Effect-seven spawns are now deferred until the next callback, while a surviving slot two retains its original ball number for the remainder of a callback after slot one exits.
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
Store capture progression with each recovered ball slot and pass the original one-based ball number through type-3 and type-4 handling. Multiball captures now remove only the captured slot, promote the survivor when slot one exits, and preserve the effect-seven argument guard that prevents ball two from spawning a third ball.
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
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
Replace the shared bumper cooldown and synthetic circle outlines with the
original per-record countdown bytes. Bumpers 51-53 use five active callbacks,
side targets 140-147 use twenty, and top targets 150-152 use ten; countdowns
pause while the ball is idle and decrement once per active detail callback.
Render each active record by copying its exact inclusive binary render bounds
from overlay A at the same destination, then naturally fall back to overlay B
when the countdown reaches zero. This removes invented vector effects and keeps
visual lifetime tied to gameplay timer state.
Test Plan:
- `cargo test --all-targets` -- passed, 75 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Replace the free-running 68x60 wheel animation with the original six-callback
91x90 DAT600 mechanism. Start WAVE 2011 at state one, advance once per selected
detail timer, and rotate the five per-player lock contact/item values exactly at
state six.
Decode the 17x17 DAT600 target image/mask pair and render all six recovered
position sets over the correct atlas or overlay-B base. Preserve filled targets
in their resting state after rotation and add a deterministic `targets` scenario
for trace and framebuffer validation.
Test Plan:
- `cargo test --all-targets` -- passed, 74 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md README.md RECONSTRUCTION.md` -- passed
- rendered all six target-rotation states -- visually verified
- `git diff --cached --check` -- passed
Replace the immediate five-hole cleanup with the original 281-callback panel
state machine. Suspend physics, clear contact/item state at the recovered
boundaries, and emit WAVE 2013, WAVE 2012, and stop events at frames
1/45/58/66/129/159/222/230/238 before resuming after cleanup.
Decode DAT600's four AND/OR mask-image pairs into transparent native textures
and render all nine phase formulas with the original source rectangles,
destination offsets, variable heights, and opaque wheel copies. Add a `panel`
simulation scenario for deterministic trace and framebuffer validation.
Test Plan:
- `cargo test --all-targets` -- passed, 72 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md README.md RECONSTRUCTION.md` -- passed
- rendered panel frames 1, 66, 159, 238, and completion -- visually verified
- `git diff --cached --check` -- passed
Remove the invented attract-screen instruction panel so idle mode presents the
original table artwork without a modern overlay. Keep the original plus-key
start flow and automatic high-score presentation.
Stop assigning F2 and F3 to modern screens; those keys are normally inactive in
the original executable outside dormant debug paths. Retain optional portable
settings and high-score viewers on F10 and F9, documenting them as additional
host controls rather than original behavior.
Test Plan:
- `cargo test --all-targets` -- passed, 70 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md README.md RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Replace the host-frame 10 ms publication loop with the original timer matrix:
50/40/30/20/10 ms callbacks execute exactly 5/4/3/2/1 internal physics
substeps. Ball state, claw progression, and flipper geometry are now visible only
at the selected callback boundary rather than leaking intermediate substeps.
Separate flipper input flags from flipper position. Key changes are sampled by
the timer, then produce one delta edge, WAVE 2021, collision response, and
rendered position update after physics, matching the Win16 callback order. Clear
both input and position state on turn reset and keep tilt decay on the same timer
cadence.
Test Plan:
- `cargo test --all-targets` -- passed, 70 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md README.md RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Add a bit-exact six-byte Real48 implementation for integer conversion,
rounding, comparison, add/subtract, multiply/divide, and Newton square root.
Use the normalized Borland random-register result and route speed clamps,
segment/circle detection, moving flippers, captures, triggers, and magnetic
fields through the recovered arithmetic instead of host floating formulas.
Preserve Real48 spin per ball and feed it through the original tangent/spin
response, separating zero-spin C fixtures from retained-spin Wine traces.
Replace path-progress selection with surface-distance ordering and implement
dynamic records 174/175, including impulse transfer to the other slot,
normal_velocity-1000 response, and the second post-collision speed clamp.
Test Plan:
- `cargo test --all-targets` -- passed, 69 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Replace the shared boolean sensor latch with the two distinct states used by the
binary: per-player 16-bit contact words for type-three records and transient
entry flags for type-four triggers. Only active/contact record state is mirrored
across player turns, matching the original save/load loop.
Port the type-three deep-inside threshold, velocity damping and 150-millipixel
pull, stationary gate, first-contact sound and ball-number publication, shared
capture age in five-unit steps, and the 99/2 completion sentinels. Lock holes and
the reset pocket now complete after the recovered hold interval; the claw
publishes its sound on contact and starts on the following completed state.
Test Plan:
- `cargo test --all-targets` -- passed, 63 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Remove the live-fitted upward and downward flipper polynomials. Port the
reconstructed 1000:7ed9 path with delta-specific pivots and record edges,
integer cross-product and radius gates, penetration, response-record gain,
wrapping velocity updates, and the final position delta.
Use resting records on press and the already-raised records on release, correct
the original delta direction, and apply each edge to both live ball slots. Keep
render-facing Vec2 projections, but adjust their float representation by ULPs so
every gameplay millipixel round-trips exactly instead of losing reconstructed
integer results.
Test Plan:
- `bash original/tools/test_reconstructed_c.sh` -- passed during raised-record reference capture
- `cargo test --all-targets` -- passed, 62 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
The rewrite used a 375-millipixel impulse for both charging and release. The
reconstructed key handlers use the setup scalar 15: every repeated Down keydown
subtracts 15*50, and release subtracts a further 15*100 before clamping.
Use 750-millipixel press increments and the 1,500-millipixel release impulse,
then reproduce the randomized -3800 lower clamp and weak -2280 upper branch.
Derive the ten launcher decoration frames from the original strict multiples of
750 and keep integration/gravity assertions separate from the immediate release
state.
Test Plan:
- `cargo test --all-targets` -- passed, 60 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
The rewrite scored type-four targets but left ball velocity unchanged. The
reconstructed collision loop consumes two Borland random values after each
entered radius-bearing trigger and scales X and Y independently by
1.03 - Random * 0.08.
Apply that response to targets 140 through 152 and effect record 149 after their
ordered rule handling. This restores both the immediate trajectory and the
shared random-stream position used by later launcher, claw, nudge, and mechanism
choices.
Test Plan:
- `cargo test --all-targets` -- passed, 60 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
The rewrite waited for the whole game to finish, selected only the maximum
player score, and sorted entries as ordinary unsigned integers. The original
checks each player immediately when their own last ball is lost and compares
scores by signed high word then unsigned low word.
Emit a candidate at each player elimination, prompt qualifying players with the
original `TDK Pinball Player` value, show the table, and then either resume the
next player or return to attract mode. Preserve source table order on import and
insert only strictly qualifying scores with the original word-wise comparator;
keep JSON as the host persistence representation.
Test Plan:
- `cargo test --all-targets` -- passed, 60 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
The rewrite reset wheel holes, top targets, collision latches, active records,
selected effects, and multiball readiness on every drain. The reconstructed
turn-state code instead saves all 175 live record states for the outgoing player
and restores the incoming player's mirror.
Add a per-player rule snapshot and save/load it at normal turn transitions.
Keep transient ball, claw, tilt, and presentation state global while retaining
each player's mechanism progress and contact/activation state across later
turns and extra balls.
Test Plan:
- `cargo test --all-targets` -- passed, 58 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Replace the global bonus approximation with the original per-player secondary
score and multiplier. Lock holes now accumulate 10k, 20k, 40k, 80k, and 160k;
the fifth transfer applies the current multiplier, clears the secondary score,
increments that multiplier, and grants the recovered extra-ball count.
Route effects 1 through 5 into the same secondary score and make effect 6
transfer its displayed multiplied value before clearing it. Render that value
on the table and stop adding an unrelated global bonus during drain, preserving
secondary progress across balls as the original player state does.
Test Plan:
- `cargo test --all-targets` -- passed, 57 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Replace the float nudge direction, cooldown, and decaying meter with the
scan-code-specific Win16 behavior. Left Shift and keypad 3 now use the original
random directional impulse, while Space uses the reconstructed random horizontal
and vertical formulas with scalar 15 and wrapping millipixel velocities.
Track the original 16-bit tilt counter: each nudge adds 25, compares against
30 + Random(10), and the selected detail timer decrements a nonzero counter once
per callback. Allow the same input while a ball waits in the launcher, retain
bonus state when tilt latches, clear flippers immediately, and keep Right Shift
only as a documented modern alias for keypad 3.
Test Plan:
- `cargo test --all-targets` -- passed, 57 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/README.md tdkpin-rs/RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Replace the Xorshift state with the reconstructed Borland Win16 generator,
including its wrapping linear update and high-word Random(n) mapping. Route
launcher clamp variation, effect selection, and claw terminal choices through
one shared stream so seeded event order follows the executable.
Use the same stream for the recovered magnetic-field response. Fields now scale
horizontal speed by 0.9 and choose the upward impulse from
-3800 * (1 - Random * 0.3) instead of assigning a fixed -3000 velocity. Adjust
deterministic scenario expectations where the original stream permits a second
natural claw capture or does not force one during a particular autoplay seed.
Test Plan:
- `cargo test --all-targets` -- passed, 56 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
The rewrite assigned one WAV resource to each logical event, which could not
represent the original call-site behavior. It played reserved WAV 2022 for
media awards, omitted multi-sound bank sequences, and used the wrong cues for
captures, bumpers, targets, nudges, and tilt.
Emit explicit sound-resource events at the reconstructed C control-flow sites
so logical rendering events no longer imply audio. Move media threshold
advancement into score addition, retain one threshold transition per call, and
use wrapping 32-bit score arithmetic. Restore the ninth-diamond 24,464-point
completion, the subsequent double-score activation, and later 100,000-point
secondary-score awards.
Test Plan:
- `cargo test --all-targets` -- passed, 53 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
Replace the partial mechanics transcriptions with a separate, readable C11
reconstruction of the complete Win16 image while preserving the original raw
Ghidra export as immutable evidence. Cover all ordinary and overlapping entry
points, Borland runtime behavior, Win16 imports, segmented data, callbacks,
resources, indirect control flow, physics, rendering, persistence, and
startup/shutdown lifecycles.
Add deterministic extraction and audit tooling plus address-linked ledgers for
functions, imports, DGROUP ranges and objects, relocations, resources, and
callbacks. The final gate records zero raw, partial, restored, unknown,
blocked, or unclassified required units. Keep the semantic-fidelity boundary
explicit: the portable C is not claimed to reproduce a byte-identical Borland
NE build.
Add strict focused harnesses for every reconstructed C unit, exact resource
round-trip checks, and a 16-bit Borland Real48 reference probe. No Rust source
or Cargo metadata is changed in this phase.
Test Plan:
- `bash original/tools/test_reconstructed_c.sh` -- passed
- `bash original/tools/probe_real48_reference.sh` -- passed bit-for-bit
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `git diff --cached --check` -- passed
- `git diff HEAD -- '*.rs' Cargo.toml Cargo.lock` -- empty
The recovered client enlarged the 640x460 canvas to 960x690 and shrank the
score glyphs, so artwork and numeric displays no longer matched the original
pixel presentation. Restore the native window dimensions and title, render
score digits at their recovered 16x28 size, and align the score fields to the
original layout. Update the user and reconstruction documentation accordingly.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- passed, 52 tests
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `rumdl check --flavor commonmark CHANGELOG.md README.md RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
- `cargo +nightly fmt --all -- --check` -- baseline fails on existing import formatting in touched and untouched Rust files
- `prettier --check CHANGELOG.md README.md RECONSTRUCTION.md --prose-wrap always --print-width 80` -- baseline fails on existing whole-file formatting in README.md and RECONSTRUCTION.md