Keep browser settings and the existing local save fallback, but route the
browser high-score table through the same-origin service when it is available.
The WASM storage bridge now accepts fetched score JSON and queues one validated
submission at a time. The JavaScript plugin fetches the canonical table,
submits accepted name-entry scores, ignores stale responses, retries failures,
and feeds successful responses back into the running game.
Update the web and project documentation to describe the optional shared
leaderboard and regenerate the tracked browser artifact. A missing service
continues to leave the local table usable; the service documentation records
that anonymous client scores are intentionally not tamper-resistant.
Test Plan:
- `just test` -- passed (3 service tests and 137 game tests)
- `just clippy` -- passed
- `just web-build` -- passed
- `cargo +nightly fmt -- --check` -- passed
- `node --check web/storage.js` and `prettier --check web/storage.js` -- passed
- `rumdl check --flavor commonmark CHANGELOG.md README.md web/README.md highscore-server/README.md` -- passed
- Browser WASM load through same-origin API proxy -- passed
- `git diff --cached --check` -- passed
Build TDK Pinball / build (macos-latest) (push) Canceled after 0s
Build TDK Pinball / build (ubuntu-latest) (push) Canceled after 0s
Build TDK Pinball / build (windows-latest) (push) Canceled after 0s
The interactive build inherited Macroquad's fixed zero random state, making
maximum-power launcher shots repeat across fresh runs. Seed the platform
generator from the startup clock before taking the program-global gameplay
seed. Require record 148's still-live contact before the effect-seven special
respawn and clear active multiball state on an ordinary ball drain, so a
released reserve ball cannot be recreated after both slots are lost.
Type-4 target and effect handlers now honor each record's predicted broadphase
before changing its entry latch. This preserves the latch while another
multiball slot is elsewhere, preventing repeated upper-left corridor scoring.
The regressions and regenerated browser artifact stay with the implementation.
Test Plan:
- `just test` -- passed (137 tests)
- `just clippy` -- passed
- `cargo build --profile production` -- passed
- `just web-build` -- passed
- `cargo +nightly fmt --check` -- passed
- `rumdl check --flavor commonmark CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
Expose the existing Macroquad game as a static WASM website while preserving
native desktop behavior. Native-only simulation/file-export code and the
per-user filesystem save path are now separated from the browser build.
The browser version uses a small WASM-only storage support crate and a
Macroquad-compatible JavaScript plugin to persist the same JSON settings and
high scores in localStorage. Browser audio decoding starts in an owned
background coroutine so the game can render its original loading/attract
screens while the embedded sounds finish loading. The checked-in web bundle
contains the optimized WASM, centered black HTML shell, and build/serve
instructions.
Test Plan:
- `just test` -- passed, 135 tests
- `just clippy` -- passed
- `cargo clippy --target wasm32-unknown-unknown -- -D warnings` -- passed
- `cargo +nightly fmt --check` and web-storage format check -- passed
- `just web-build` -- passed; packaged WASM matches the production artifact
- Browser smoke test at `http://127.0.0.1:8000/` -- rendered the centered
game, started gameplay, opened settings, and restored a changed language
from browser storage in a fresh page with no runtime errors
The same raw Div32 pattern used by the item animation appears in every nested
idle phase: after division by 32, 40, or 16, the binary copies the remainder
from CX:BX before the second division. Readable C and Rust used quotients for
targets, word quads, the record strip, and the four-point chase, turning short
repeating chases into slow one-shot progressions.
Use `%32/2`, `%32/4`, `%40/4`, and `%16/4` for those cycles alongside the
already corrected `%144/8` items. Preserve the first-cycle phase-zero delay,
then repeat each incremental overlay state exactly. Add C and Rust coverage at
phase activation, reversal, deactivation, and wrap boundaries.
Test Plan:
- raw instruction review at `1000:01b7-063b` -- all remainder transfers 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
- `cargo run -- --simulate attract --step 145 --screenshot /tmp/tdkpin-attract-remainder.png` -- passed; visually inspected at 640x460
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
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 original RandSeed is program-global and is initialized once at startup.
Rust stored the exact Borland generator inside Game but discarded its final
state at game over and seeded every following game from Macroquad again,
breaking stream continuity across high-score/attract flow.
Expose the current seed, retain it when the App consumes a finished game, and
construct the next game from that value. Attract, high-score, and portable UI
continue to consume no gameplay draws.
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
- `git diff --cached --check` -- passed
Both original key handlers return without processing gameplay keys while any of
the four child-window slots is occupied. Rust continued to toggle sound through
Help, HighScore, and name-entry UI and used F1 to close Help, behavior the main
Win16 window cannot perform through an active child.
Limit recovered F1 and F12 handling to the attract and playing screens. The
portable Enter/Escape Help return remains available as an explicit modern
control without bypassing the original main-window gate.
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
- `git diff --cached --check` -- passed
The original key-release handler owns add-player, all three nudge actions, and
F12 sound toggling. Rust dispatched those actions on the initial key press,
which changed launcher/player timing and applied nudges before the player
released the key.
Use Macroquad's key-up edges for every recovered release-handler action. Keep
F1 on key-down, flippers as held key bytes, and the launcher as press/repeat
plus release, matching their separate binary paths.
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
- `git diff --cached --check` -- passed
The Win16 handlers mask the keyboard scan to its low byte. Main and keypad
Enter therefore both produce the right-flipper scan 0x1c, both Ctrl keys
produce the left-flipper scan 0x1d, and scan 0x1b is the physical
main-keyboard plus/right-bracket position accepted alongside keypad plus. Rust
omitted main Enter and scan 0x1b, and incorrectly assigned Right Ctrl to the
right flipper.
Map the native keys to those recovered scan semantics. Keep A/D, arrows, and
Equal only as explicit modern aliases and document the distinction.
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 README.md 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
Record 148 publishes player item 20 and refreshes word-quad 20 through overlay
A. Its recovered bounds are (9,14)-(25,30). Rust tracked the armed special hole
but never rendered this visible 17x17 table state.
Expose the armed/active special-hole state and composite the exact DAT997 region
until the special respawn consumes record 148's contact. Make the deterministic
effect-seven scenario internally valid by seeding the required contact, so its
framebuffer now covers both the DAT407 effect and item 20.
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 effect-7 --step 0 --screenshot /tmp/tdkpin-effect7-special.png` -- passed; visually inspected at 640x460
- launcher/effect screenshot comparison -- 136 RGB pixels differ inside only the expected 17x17 item region
- `git diff --cached --check` -- passed
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
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
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
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
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
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
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
Implement the flagged object-84 selector and initially inactive type-4 object
149. Choose a different effect from 1-6, render the enabled target from its
original active-table rectangle, award its static 500 points, then apply the
recovered bonus or cashout action and disable it until reselected.
This replaces the final unimplemented help-screen target effect with its
original dispatcher values.
Test Plan:
- `cargo test --all-targets` -- 49 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- live object-84 probe activated record 149 and selected effect 3
- effect selection/consumption test -- passed
- `git diff --cached --check` -- passed
Replace the global timed magnet and drain teleport with the three recovered
field records. Map top targets 150-152 to center field 6 and outlane fields
153-154, apply upward pull only inside each registered rectangle, and deactivate
the field after it pulls the ball through its upper boundary.
Render each active field from its exact original active-table rectangle instead
of drawing invented yellow triangles.
Test Plan:
- `cargo test --all-targets` -- 48 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- production build plus Windows GNU and macOS checks -- passed
- live field-153 entry direction matched and Rust field lifecycle test 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
Remove the Enter shortcut so the start flow matches the original plus-key handler. Use the recovered player-add sound resource for both the first and subsequent players.
Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- git diff --cached --check
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
Replace the broad robot bounce rectangle with the recovered object-89 capture state machine. Suspend the live ball, advance the exact sprite rows, release from decoded original coordinates and directions, and keep the surrounding collision records disabled until the arm returns home.
Render the 900x296 sheet as 100x74 frames at the original destination and suppress the duplicate ball while it is embedded in the capture animation.
Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- git diff --cached --check
Play the original WAVE resource for both movement edges of each flipper and centralize gameplay event sound selection.
Replace guessed control and mechanism mappings with the resource numbers recovered from the Win16 call sites. Keep synthetic ball search silent because it has no original trigger.
Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- git diff --cached --check
Match the recovered Win16 registration flow: the first plus press creates Player 1 and additional presses add Players 2 through 4 until the initial launch. Down now belongs only to charging and releasing the plunger.
Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- live: verify Down cannot leave attract mode
- live: start with plus, add Player 2, hold Down through partial/full plunger frames, and release onto the table
- git diff --check
Advance the recovered eleven-frame plunger over a full one-second hold and use that same frame selection for rendering. This makes a short tap visibly and physically weaker while a complete pull still clears the shooter lane.
Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- git diff --check
Replace the reversed oversized flipper vectors with the original binary rest
and raised geometry. Render the exact resource 997 flipper rectangles while
pressed and keep the live collision capsules aligned with those sprites.
Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- compare forced raised-state rendering with the extracted resource rectangles
Compose the running table from the recovered resting bitmap and exact active
UI regions. Remove the procedural replacements that distorted lamps,
flippers, player rows, and display values, and place the plunger strip at its
recovered destination.
Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- launch the game and compare the initial frame and charged plunger visually
Start each ball at the recovered right-lane coordinates and charge the
plunger while Down is held, firing only on release. Render all eleven frames
of resource 901, preserve object 25 as the returning-ball catch, and use the
object table's center-contact extents without adding the art radius twice.
Test Plan:
- cargo fmt --all -- --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- runtime smoke: start, hold Down, release, and inspect the right shooter lane
Replace the mirrored, partial wall transcription with all active static line
and circle objects from the Win16 registration table. Preserve the original
object IDs for drain and shooter-stop handling, and use bounded physics
substeps so fast balls cannot skip thin rails.
Test Plan:
- cargo fmt --all -- --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- git diff --check
High-DPI sizing and a resizable client allowed the 640x460 canvas to be placed
inside a much taller window, producing the large internal black band captured in
the gameplay recording. Open an aspect-correct fixed 960x690 client with high-DPI
framebuffer scaling disabled, giving every platform a predictable 1.5x canvas.
Use exact active-table patches for the cyan score fields and derive transparent
numeral glyphs from the recovered Win16 bitmap font. Fill the complete display
slots, move the KBytes readout clear of its border, and place shortcuts on a
readable footer so no dynamic text is clipped or left over an inactive panel.
Test Plan:
- `cargo fmt -- --check` -- passed
- `cargo test --all-targets` -- passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- launched the Linux window and confirmed a 960x690 client with no letterbox,
full-width cyan fields, recovered score digits, and readable footer -- passed
- `git diff --check` -- passed
The gameplay renderer drew the inactive table and then placed thin line flippers
and procedural circles over baked resting artwork. This left two flipper poses
visible at once, made the ball difficult to see, and ignored recovered animation
resources already embedded with the reconstruction.
Build an alpha-masked texture from the original ball bitmap and mask, erase the
baked flipper pose before drawing collision-aligned moving flippers, and animate
the recovered wheel, robot, and magnet sheets from their gameplay events. Add
bumper flashes, exact lit-table pixels for TILT, and a short visual table shake
for nudges so contacts and machine state have immediate feedback.
Test Plan:
- `cargo fmt -- --check` -- passed
- `cargo test --all-targets` -- passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- launched the Linux window and inspected attract, gameplay, resting flippers,
raised flipper cleanup, and the masked ball sprite -- passed
- `git diff --check` -- passed
Starting from the attract screen previously created an idle ball and required a
second Down press to launch it. Launch the first ball as part of the start action
while retaining the normal launch action for later balls.
Replace the decaying visual-only tilt value with a latched game state. A tilt now
forfeits the current bonus, suppresses scoring and further nudges, returns both
flippers to rest, and remains active until the ball drains. Keep the warning on
the playfield and use the recovered tilt sound instead of treating the state as
a temporary decorative overlay.
Test Plan:
- `cargo fmt -- --check` -- passed
- `cargo test --all-targets` -- passed, including tilt latch, scoring, flipper,
drain-reset, and launch coverage
- `cargo clippy --all-targets -- -D warnings` -- passed
- `git diff --check` -- passed
The recovered bottom apron had been represented as one solid collision segment,
which prevented the ball from reaching the drain threshold. Split that apron
around the visible center drain so a lost ball decrements the ball count and
allows player and game-over progression to continue.
Add a conservative ball-search impulse after three seconds below the movement
threshold. This protects long-running games from other low-energy rests without
changing active trajectories, and reuses the original nudge sound as feedback.
Test Plan:
- `cargo fmt -- --check` -- passed
- `cargo test --all-targets` -- passed, including drain and ball-search regressions
- `cargo clippy --all-targets -- -D warnings` -- passed
- `git diff --check` -- passed
Replace the empty Rust scaffold with a playable native reconstruction of the
1995 Win16 game. Embed the complete decoded asset set, retain the original
640x460 presentation and localized help, and implement a resizable letterboxed
Macroquad frontend for Linux, macOS, and Windows.
Recreate the multiplayer ball flow, flippers, nudging, target banks, wheel,
robot, lock, magnets, bumper progression, TDK diamond multiplier, media extra
balls, sound dispatch, settings, and high-score entry. The physics engine uses
a fixed time step and 101 static collision segments transcribed from the
original 175-object table. Portable JSON persistence imports and decodes the
original XOR-obfuscated high-score file on first run.
Document the evidence boundary explicitly: artwork and PCM samples are exact,
static geometry is recovered, and several numeric scoring/impulse values remain
best-evidence behavioral tuning rather than bit-identical Win16 arithmetic.
Add a native three-OS CI matrix and retain every decoded resource for future
fidelity work.
Test Plan:
- `diff -qr original/assets/decoded tdkpin-rs/assets/original` with the two
intentionally added legacy root files excluded -- passed
- `cargo fmt --all -- --check` -- passed
- `cargo check --all-targets` -- passed
- `cargo test --all-targets` -- passed, 8 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- `cargo check --all-targets --target x86_64-pc-windows-gnu` -- passed
- `cargo check --all-targets --target x86_64-apple-darwin` -- passed
- `cargo run` graphical start, launch, collision, and score smoke test -- passed;
audible output was unavailable because the host has no ALSA device