Raw 1000:061c-063b divides the idle tick by 144, copies the remainder from
CX:BX into AX:DX, and only then divides by 8. Readable C and Rust instead used
an unbounded quotient equivalent to tick/1152, making DAT801-809 advance far
too slowly and suggesting a nonexistent negative long-idle index.
Use `(tick % 144) / 8` to repeat item states 0,1..9,8..1 every 144 callbacks.
Remove the disproven long-idle status crops and restore the ten-minute simulator
ceiling. Extend the C and Rust phase tests across the forward, reverse, and wrap
boundaries.
Test Plan:
- raw instruction review at `1000:061c-063b` -- remainder transfer confirmed
- `cargo test --workspace --all-targets --all-features` -- 125 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 with zero incomplete or unclassified units
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
The attract item animation computes `18 - phase` as a signed value without a
lower bound. At phases 19 through 21, the subsequent 16-bit handle index wraps
backward from the DAT801 table into DAT703, DAT702, and DAT701, copying each
bitmap's upper 68x61 region into the item panel. Rust stopped drawing after
phase 18 and hid this observable long-idle behavior.
Render those three adjacent status crops and return to the inactive panel for
later phases. Raise the deterministic simulation ceiling to 100,000 frames so
the first wrapped phase at 10.94 minutes can be captured and inspected.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 125 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `cargo run -- --simulate attract --step 78797 --screenshot /tmp/tdkpin-attract-wrap-19.png` -- passed; DAT703 crop visually inspected at 640x460
- `git diff --cached --check` -- passed
The left relative slingshot probe proved the spin projection, but its geometry
did not distinguish the sign rule. A stopped-Wine probe on symmetric record 72
did: the binary uses the standard dot-product tangent for both spin and the
opposing direction of the current tangential response. The earlier
swapped-component interpretation happened to agree for vertical walls and a
centered circle but selected the wrong sign on the right slingshot.
Use `tv=(vx*nx+vy*ny)/length`, update spin with
`tv*0.02*response_tangent`, and apply
`-sign(tv)*abs(normal_velocity)*response_tangent`, retaining the binary's
negative direction when tv is zero. Seal record 72's exact position, velocity,
and `+5.46` Real48 spin in C and Rust. Keep the two-minute autoplay assertion
focused on actual launch, press/release, target, bumper, and finite-state
activity after the corrected trajectory.
Test Plan:
- stopped Wine 11.15 record-72 injection/trace -- matched position, velocity, and spin
- `cargo test --workspace --all-targets --all-features` -- 124 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 with zero incomplete or unclassified units
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
Records 90 through 104 and 109 through 120 deactivate in linked three-line
groups. For each group, the flag-bearing head switches its recovered bounds
from overlay B to overlay A until the complete bank rearms every record. Rust
updated collision activity but never drew these nine intermediate states.
Render the five bumper-bank and four TDK-bank head regions directly from the
175-record ledger. Extend the bank regression to prove partial activation and
post-completion clearing, and seed two partial groups in the deterministic
rules framebuffer.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 120 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `cargo run -- --simulate targets --step 26 --screenshot /tmp/tdkpin-targets-banks.png` -- passed; partial bumper and TDK bank regions visually inspected at 640x460
- `git diff --cached --check` -- passed
The five word quads at indices 1 through 5 are the visible progression for the
recovered bumper-value byte. The original activates them cumulatively as the
value rises from 1,000 to 6,000. Rust implemented the score progression but did
not render any of these player items during gameplay.
Share the exact quad bounds with the attract animation and draw the first
value/1000-1 regions from DAT997. Seed the deterministic targets scenario with
the maximum recovered value so framebuffer validation covers all five lamps.
Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 120 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `cargo run -- --simulate targets --step 26 --screenshot /tmp/tdkpin-targets-bumper.png` -- passed; all five recovered regions visually inspected at 640x460
- `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
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
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
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
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
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
Arm effect 7 after the wheel-reset target, select it on the next effect line,
and spawn a second visible live ball when record 149 is consumed. Advance the
second ball through recovered line, circle, ball-ball, magnetic, and scoring
collisions independently.
During multiball, draining one ball leaves the other active without consuming a
remaining player ball. Include secondary state in deterministic traces and
finite-state validation.
Test Plan:
- `cargo test --all-targets` -- 52 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- production build plus Windows GNU and macOS checks -- passed
- effect-seven spawn, movement, and single-drain lifecycle test -- passed
- `git diff --cached --check` -- passed
Add a seeded autoplay scenario that performs a deliberate full-strength charge
for every launcher ball and operates both flippers from live ball position.
Turn the two-minute run into an end-to-end test covering repeated launches,
flipper edges, bumpers, targets, lock holes, claw capture/release, and drains
while rejecting non-finite state.
Document autoplay as the long-run validation entry point and record the expanded
runtime coverage boundary.
Test Plan:
- `cargo test --all-targets` -- 48 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- Windows GNU and macOS x86_64 `cargo check` -- passed
- `git diff --cached --check` -- passed
Replace the guessed variable-rate floating integration with the original 10 ms
millipixel step, 15-unit gravity, and 3800-unit speed limit. Restore every
registered type-2 Real48 response pair, one-sided start-to-end orientation, and
the original normal/tangent velocity transform validated against live shooter
wall traces.
Restore repeat-driven plunger charging and its release-time randomized clamp.
The shooter route now follows table orientation instead of coordinate-specific
gate exceptions. Remove the synthetic ball-search impulse so future stuck balls
remain visible reconstruction failures, and include collision ids in deterministic
traces for differential work on circles and triggers.
Test Plan:
- `cargo test --all-targets` -- 42 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- charged launcher scenario cleared the shooter lane -- passed
- all recovered rails accepted front-side and rejected back-side probes
- `git diff --cached --check` -- passed
Add named launcher, flipper, and claw scenarios that advance at exact 120 Hz steps with stable seeds. Emit per-step JSON state/event traces and optionally capture the logical 640x460 framebuffer at any requested time or step.
Cover parser behavior, deterministic replay, launcher timing, flipper edges, and complete capture/release/idle cycles for all claw terminals.
Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- cargo run -- --simulate claw-6 --at 0.15 --screenshot /tmp/tdkpin-sim-validation/claw-6-after-flush.png --trace /tmp/tdkpin-sim-validation/claw-6.json
- Inspect both generated 640x460 PNGs and JSON traces
- git diff --cached --check