Commit Graph
100 Commits
Author SHA1 Message Date
ddidderr 8e1a1c91e2 feat(highscores): seed new server databases with demo scores
New SQLite high-score databases were previously created empty, so the first
shared table had no original entries. Seed only database paths that did not
exist before opening with the ten distributed demo scores, while leaving
existing files and in-memory stores unchanged. Add coverage for both first
creation and existing empty files, and document the behavior.

Test Plan:
- `just test-highscore-server` -- passed (8 tests)
- `just clippy-highscore-server` -- passed
- `cargo +nightly fmt --manifest-path highscore-server/Cargo.toml -- --check` -- passed
- `git diff --cached --check` -- passed
2026-08-29 20:44:05 +02:00
ddidderr 9023af7e7e fix(web): define high-score retry bounds
The browser storage plugin initializes and updates its high-score retry
backoff, but the constants supplying its initial and maximum delays were
lost during the merge that combined the retry and relative-endpoint fixes.
Define the intended 250 ms initial delay and 30 s cap so the plugin can load
and retain bounded retry behavior after transient submission failures.

Test Plan:
- `node --check tdkpin-rs/web/storage.js` -- passed
- Node VM top-level load harness -- passed
- `prettier --check tdkpin-rs/web/storage.js` -- passed
- `cargo +nightly fmt --all -- --check` -- passed
- `just test` -- passed (144 tests)
- `just clippy` -- passed
- `git diff --cached --check` -- passed
2026-08-29 20:18:53 +02:00
ddidderr 12d1ec0aab Merge branch 'tomerge' 2026-08-29 20:11:13 +02:00
ddidderr 9c5b033c0a fix(web): use relative path for high-score API endpoint
Change the high-score endpoint URL in storage.js from "/api/highscores" to
"./api/highscores". When serving the web build from a subpath rather than the
domain root, an absolute path sends fetch requests to the domain root instead
of the nested application path. Using a relative URL ensures requests resolve
relative to the active document path while still working when hosted at root.

Test Plan:
- `node --check tdkpin-rs/web/storage.js` -- passed
- `npx prettier --check tdkpin-rs/web/storage.js` -- passed
- `just test` -- passed (138 game tests, 3 highscore-server tests)
- `just clippy` -- passed
- `git diff --cached --check` -- passed
2026-08-29 20:08:15 +02:00
ddidderr bc1ebcaaaa fix(web): back off high-score retries
The browser previously retried every failed shared high-score submission on the
50 ms polling interval. With server-side rate limiting and database load
shedding, immediate 429 and 503 responses could create a tight retry loop.
Add a bounded exponential delay with jitter, honor Retry-After on transient
responses, and reset the delay after a successful submission. Keep the pending
revision retryable without allowing overlapping requests.

Test Plan:
- `node --check tdkpin-rs/web/storage.js` -- passed
- `prettier --check tdkpin-rs/web/storage.js` -- passed
- Node VM retry timing harness covering Retry-After, backoff, and reset -- passed
- `git diff --cached --check` -- passed
2026-08-29 19:49:49 +02:00
ddidderr 3dff722535 fix(highscores): bound service resource usage
The high-score endpoints previously accepted unbounded request bodies and ran
SQLite work directly in async handlers, allowing oversized input or database
contention to consume server resources. Add a 1 KiB route body limit, admit
only one database operation at a time, shed excess requests with a clear 503,
and run accepted SQLite work on blocking threads while retaining admission
until that work finishes. Extend the Nginx example with matching request,
connection, body, and proxy time limits, and cover the limits, health
availability, contention, and cancellation behavior with tests.

Test Plan:
- `just --justfile tdkpin-rs/justfile test` -- passed (144 tests)
- `just --justfile tdkpin-rs/justfile clippy` -- passed
- `cargo +nightly fmt --manifest-path tdkpin-rs/highscore-server/Cargo.toml -- --check` -- passed
- `rumdl check --flavor commonmark tdkpin-rs/highscore-server/README.md` -- passed
- `git diff --cached --check` -- passed
2026-08-29 19:36:53 +02:00
ddidderr 7ee2e71bc7 fix: relative api/highscores path 2026-08-29 18:46:10 +02:00
ddidderr 86434aaa2b fix: remote unused github workflow 2026-08-29 18:07:54 +02:00
ddidderr 51502ef92f chore(release): prepare v1.1.0
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
Bump the package version to 1.1.0 for the web port, optional shared high-score service, and post-1.0 parity and edge fixes. Update CHANGELOG.md and refresh the tracked web WASM artifact.

Test Plan:
- `just test` -- passed (138 game tests, 3 highscore-server tests)
- `just clippy` -- passed
- `just build-production` -- passed
- `just web-build` -- passed
- `cargo metadata --locked --format-version 1 --no-deps` -- passed
- `git diff --cached --check` -- passed
2026-08-29 17:52:53 +02:00
ddidderr f9063bd2ff chore(web): regenerate browser artifact
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
2026-08-29 17:50:49 +02:00
ddidderr a36856e526 fix(game): allow launcher input while tilted
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
Preserve the original launcher path when a nudge tilts a game before launch.
The Win16 press and release handlers process scan code 0x50 without consulting
`game_tilted`, so Tilt disables flippers and scoring but does not strand a ball
in the shooter lane. Remove the extra Rust gate, retain the original launch
sound suppression during Tilt, and add regression coverage for the waiting-ball
case.

Test Plan:
- `just test` -- passed (138 Rust tests and 3 highscore-server tests)
- `just clippy` -- passed
- `cargo +nightly fmt --all -- --check` -- passed
- `git diff --check` and `git diff --cached --check` -- passed
2026-08-29 17:46:24 +02:00
ddidderr acb0c20b59 fix(web): vendor the Macroquad browser loader
The page depended on the externally hosted miniquad bundle, which violates a
same-origin `script-src 'self'` policy and makes the game depend on a third
party at runtime. Vendor the current official loader alongside the web assets
and move the WASM `load` call into a local bootstrap script, preserving plugin
registration order without inline JavaScript.

Document the CSP requirement for WebAssembly compilation and same-origin
connections. The vendored bundle is the current response from the official
Macroquad loader URL and was syntax-checked before committing.

Test Plan:
- `just web-build` -- passed
- `node --check web/mq_js_bundle.js web/storage.js web/bootstrap.js` -- passed
- `prettier --check web/storage.js web/bootstrap.js` -- passed
- Browser smoke test with `script-src 'self' 'wasm-unsafe-eval'` and `connect-src 'self'` -- passed; 640x460 canvas and no CSP/script errors
- `git diff --cached --check` -- passed
2026-08-29 17:12:43 +02:00
ddidderr a81741b470 chore(highscores): align dependency and lint standards
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
Update the high-score service to the latest compatible direct releases while
keeping the requested Cargo.toml ranges: 0.x crates use their latest minor
line and 1.x crates use their major-only range. Refresh the standalone lockfile
so it resolves axum 0.8.9, rusqlite 0.40.2, serde 1.0.229, serde_json 1.0.151,
Tokio 1.53.1, and the latest dev-tool releases.

Copy the main package's Clippy policy and make the repository's formatting
recipes cover the service. The nested crate inherits the parent rustfmt.toml,
so one configuration remains authoritative; newly required documentation and
borrow style also keep the stricter pedantic policy clean.

Test Plan:
- `cargo update --manifest-path highscore-server/Cargo.toml` -- passed
- `just test` -- passed (3 service tests and 137 game tests)
- `just clippy` -- passed with the shared lint policy
- `just fmt-highscore-server` and `cargo +nightly fmt --manifest-path highscore-server/Cargo.toml -- --check` -- passed
- `cargo tree --manifest-path highscore-server/Cargo.toml --depth 1` -- confirmed latest direct resolutions
- `rustfmt +nightly --print-config current` -- confirmed the parent rustfmt settings
- `rumdl check --flavor commonmark highscore-server/README.md` -- passed
- `git diff --cached --check` -- passed
2026-08-29 16:03:11 +02:00
ddidderr 3b7b84affe chore(highscores): ignore the local SQLite database
The service defaults to a database file in the Rust project directory when no
production path is configured. Ignore that file and SQLite sidecars so local
runs do not create accidental repository changes.

Test Plan:
- `git diff --cached --check` -- passed
- Ignore patterns verified against the default database filename
2026-08-29 15:55:41 +02:00
ddidderr 5dd7f38450 feat(web): use the shared high-score service
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
2026-08-29 15:55:15 +02:00
ddidderr ec0fdfd6b4 feat(highscores): add SQLite Axum service
Add a small standalone Axum service for the shared anonymous top-ten table.
SQLite keeps the deployment self-contained, while one transaction inserts a
validated name and score and removes entries below the canonical top ten.
Expose a health endpoint and same-origin API, with an nginx proxy block and
run instructions beside the service. Keep the service outside the game crate
so native gameplay persistence remains unchanged.

Test Plan:
- `cargo test --manifest-path highscore-server/Cargo.toml` -- passed (3 tests)
- `cargo clippy --manifest-path highscore-server/Cargo.toml --all-targets --all-features -- -D warnings` -- passed
- `cargo +nightly fmt --manifest-path highscore-server/Cargo.toml -- --check` -- passed
- Live executable health, POST, and GET smoke test -- passed
- `git diff --cached --check` -- passed
2026-08-29 15:50:04 +02:00
ddidderr 8fe9431989 fix(game): restore startup and multiball edge behavior
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
2026-08-29 15:27:28 +02:00
ddidderr a21f01c02d fix(web): keep game at original canvas size
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 browser shell previously sized the canvas to the full viewport, causing
Macroquad to scale the 640x460 game presentation as the website changed size.
Keep the canvas at the original 640x460 CSS-pixel dimensions and center it on
a black page instead. A minimum page size and scrolling preserve access to the
fixed canvas on viewports smaller than the original presentation.

Test Plan:
- `just web-build` -- passed
- `git diff --check` -- passed
- Browser smoke test -- canvas measured 640x460 and was centered at (320,130)
  in a 1280x720 viewport; the rendered game remained at the intended fixed
  presentation size with no runtime errors
2026-08-29 14:49:37 +02:00
ddidderr b079cfa196 feat(web): add browser build for TDK Pinball
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
2026-08-29 14:32:41 +02:00
ddidderr c2f1443436 fmt: just fmt (rust only)
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
2026-08-29 09:57:39 +02:00
ddidderr ee58525011 chore(release): prepare v1.0.0
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
Promote the accumulated reconstruction and gameplay fixes to the first stable
release. Refresh the lockfile with cargo update and express direct dependency
requirements using the requested major or 0.x compatibility ranges.

Test Plan:
- `cargo update --locked` -- passed; 0 packages required updates
- `just test` -- passed (135 tests)
- `just clippy` -- passed
- `just build-production` -- passed
- `cargo metadata --locked --format-version 1 --no-deps` -- passed
- `git diff --cached --check` -- passed
2026-08-29 09:44:59 +02:00
ddidderr 36f8c38d62 fix(multiball): clear stale release-ball state
The release-ball effect was inferred from the persistent multiball state,
which remains active while two balls are in play. After the first release this
left the top-left special-hole indicator lit and caused later sprite hits to
arm another release. Use record 148's contact state as the release prerequisite
and show the special-hole indicator only while that pre-release state is ready.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- passed (135 tests)
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- `git diff --check` -- passed
2026-08-29 09:36:33 +02:00
ddidderr fa3f168467 fix(flippers): match original moving-hit geometry
The clone's moving-flipper gate used the cross-product operands in the
opposite order, mirroring and narrowing the hit wedge. Right-flipper release
also used record 81's first endpoint even though the original reads its second
endpoint. Correct both geometry paths, retain the recovered swept tip bounds,
and add live-binary boundary vectors plus a dense transition regression.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- passed (134 tests)
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- `LSAN_OPTIONS=detect_leaks=0 ASAN_OPTIONS=detect_leaks=0 bash original/tools/test_reconstructed_c.sh` -- passed
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `git diff --cached --check` -- passed
2026-08-29 09:15:25 +02:00
ddidderr faf66f1ac0 docs(audit): record final player-experience parity
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
Add the requirement-by-requirement audit for the Rust rewrite. It ties visual,
audio, input, physics, timer, rule, multiball, player, persistence, random, and
build claims to the preserved binary, complete readable C, focused tests,
deterministic framebuffers, and stopped-Wine differential probes.

The audit distinguishes native-window and portable-storage substitutions from
gameplay semantics, records both relative-slingshot position/velocity/spin
probes, and lists the reproducible final gates used for the completion decision.

Test Plan:
- `cargo build --profile production` -- passed
- `cargo test --workspace --all-targets --all-features` -- 125 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- Windows and macOS `cargo check` targets -- passed
- `bash original/tools/test_reconstructed_c.sh` -- passed
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed with zero incomplete or unclassified units
- original executable, timer DLL, and preserved raw hashes -- matched
- `rumdl check --flavor commonmark README.md RECONSTRUCTION.md PARITY_AUDIT.md CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
2026-08-23 20:57:16 +02:00
ddidderr 7633ef9990 fix(attract): restore all remainder-driven cycles
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
2026-08-23 20:53:12 +02:00
ddidderr dd0299c30b fix(attract): use remainder-driven item phases
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
2026-08-23 20:50:05 +02:00
ddidderr 5b8351ca1f fix(ui): reproduce long-idle item index wrap
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
2026-08-23 20:45:54 +02:00
ddidderr db0ec0bdc4 fix(random): continue Borland seed across games
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
2026-08-23 20:43:10 +02:00
ddidderr 2a17fbeeef docs(audit): align validation claims with current evidence
The reconstruction ledger still described an obsolete function count, claimed
local Windows/macOS cross-target checks despite those standard libraries being
absent, and attributed lock, claw, drain, and repeated-launch coverage to the
current autoplay trace. Those statements were stronger than the current
artifacts prove after the corrected trajectory.

Record the final C ledger counts, distinguish configured native CI from local
Linux production validation, add both stopped-Wine slingshot probes, and route
claw/panel/lock/drain evidence to the deterministic and focused tests that
actually cover it.

Test Plan:
- `rumdl check --flavor commonmark RECONSTRUCTION.md` -- passed
- `git diff --cached --check` -- passed
2026-08-23 20:41:06 +02:00
ddidderr 0c4c18dbea fix(input): honor auxiliary-window key gate
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
2026-08-23 20:39:08 +02:00
ddidderr 1dff82aef6 fix(input): dispatch release-handler actions on key-up
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
2026-08-23 20:38:31 +02:00
ddidderr 26b7a9ecf3 fix(input): match original low-byte key scans
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
2026-08-23 20:37:45 +02:00
ddidderr 505bf0417a fix(rules): finish tilted type-three captures
The original capture handler does not request a launcher reset when a single
ball completes a type-three record during Tilt. It calls the normal ball-end
state machine instead. Rust returned Reset for every single-ball capture,
granting tilted lock-hole captures a free ball and bypassing record 148's
special-respawn decision.

Add an explicit Finish action for tilted single-ball type-three completions.
Lock holes now consume the ball with the tilted drain cue suppressed; record
148 still publishes its required contact first, so the same finish call can
consume it through the special-respawn branch without using a marker.

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
2026-08-23 20:36:29 +02:00
ddidderr c1790c7ff6 fix(multiball): start completed panel on type-three reset
The original reset helper checks contact words 129 through 133 and starts the
DAT600 panel whenever all five are nonzero. This matters after the fifth lock is
captured during multiball: the panel is initially deferred, but a later
single-ball type-three reset, including record 148, starts it even without a
second lock-hole award. Rust only started the panel from its lock completion
helper.

Mirror the five-contact gate in the shared type-three launcher reset. Extend the
multiball completion regression to prove both the special-hole reset path and
the separately capped survivor recapture path.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 124 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
2026-08-23 20:34:34 +02:00
ddidderr 23195cb9d1 fix(physics): restore standard tangent sign response
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
2026-08-23 20:33:20 +02:00
ddidderr f79652bc9f fix(physics): restore relative slingshot response
A full comparison against the 175-record ledger found stale guessed coordinates
in both relative slingshot chains. Circles 56/58/71/73 and lines 57/59/72/74
were displaced by up to 70 pixels. The corrected record-57 geometry then
exposed a second issue: readable C and Rust had collapsed two independent
binary response projections into one.

Transcribe the accumulated initializer coordinates exactly. Use the
swapped-component projection only to sign the current tangential response
`abs(normal_velocity) * response_tangent`, and use the standard dot product for
`spin_delta * 0.02 * response_tangent`. Correct the readable C evidence and
Rust together, extend live tracing with the six-byte spin field, and seal the
stopped-Wine transition from `(75530,354765)/(3000,15)` to
`(77369,356597)/(1839,1832)` with Real48 spin `-5.28` in both harnesses.

Test Plan:
- stopped Wine 11.15 record-57 injection/trace -- matched position, velocity, and spin
- `cargo test --workspace --all-targets --all-features` -- 123 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
- `python3 -m py_compile original/tools/trace_original_state.py original/tools/inject_original_state.py` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
2026-08-23 20:28:29 +02:00
ddidderr 9b56247c32 fix(audio): order bank completion cues before awards
The original bank handlers play WAVE 2017 before adding either the 50,000
maximum bumper-bank bonus or a 10,000 through 24,464 TDK award. Rust added the
award first. When that operation crossed a media marker, its WAVE 2007 cue was
therefore replaced by the late completion sound.

Queue both completion cues before their score operations. Exact regressions put
each bank one award below the first threshold and prove the monophonic resource
sequence 2012, 2017, 2007.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 121 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
2026-08-23 20:13:33 +02:00
ddidderr f53cd5ab2b fix(ui): render partial target-bank states
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
2026-08-23 20:12:45 +02:00
ddidderr 71938c0062 fix(ui): render cumulative bumper-value items
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
2026-08-23 20:11:06 +02:00
ddidderr ac254f9e45 fix(ui): render armed record 148 item region
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
2026-08-23 20:09:55 +02:00
ddidderr 8d436b8a4b fix(rules): preserve lock holes when arming effect seven
Record 148 writes player item 20 and its own type-three contact sentinel. It
does not clear lock-hole items or contacts 129 through 133; the panel completion
path owns that reset. Rust treated record 148 as a wheel-reset sensor and erased
all five lock holes immediately.

Rename the recovered mechanism to the special/effect-seven hole and preserve
existing lock progress when it is captured. Keep its multiball-ready state,
contact 2 publication, and later special-respawn suppression unchanged.

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
- `git diff --cached --check` -- passed
2026-08-23 20:07:39 +02:00
ddidderr 2d14823c04 fix(multiball): defer fifth-lock panel for survivor
The fifth type-three lock always pays and transfers its accumulated award, but
the original starts the DAT600 panel only when no multiball is active. Rust
started the panel unconditionally and suspended the surviving ball. A later
capture with all five contacts already set could also shift the award to
320,000 instead of retaining the binary's 160,000 cap.

Derive each lock award from the post-capture filled count capped at five. Keep
the transfer, multiplier increment, and extra ball during multiball, but defer
the panel until a single-ball completion. Cover the fifth multiball capture and
its survivor recapture end to end.

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
- `git diff --cached --check` -- passed
2026-08-23 20:06:27 +02:00
ddidderr d8cb7591b5 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
2026-08-23 20:04:34 +02:00
ddidderr 3859498172 fix(input): preserve Win16 flipper key latches
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
2026-08-23 20:04:07 +02:00
ddidderr 25019514c7 fix(multiball): clear record 148 contact on respawn
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
2026-08-23 20:02:32 +02:00
ddidderr a7c2779f73 fix(rules): retain active target rotation across drains
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
2026-08-23 20:01:00 +02:00
ddidderr 6e6b7f4c58 fix(rules): scope double scoring to the active ball
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
2026-08-23 19:58:39 +02:00
ddidderr df66530b86 fix(scoring): preserve bumper sound precedence
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
2026-08-23 19:56:41 +02:00
ddidderr 50764f6151 fix(scoring): restore marker comparison and hit sound order
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
2026-08-23 19:55:44 +02:00
ddidderr 4a55150fc0 fix(rules): restore ninth-diamond completion order
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
2026-08-23 19:52:28 +02:00
ddidderr 6f8c55657b fix(physics): restore swapped-component tangent driver
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
2026-08-23 19:48:30 +02:00
ddidderr 3dce159965 fix(physics): match live tangent response transitions
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
2026-08-23 19:42:23 +02:00
ddidderr 06d1010431 fix(physics): gate dynamic impulse on capture age
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
2026-08-23 19:32:23 +02:00
ddidderr 6c879a3f91 fix(game): restore effect-seven special respawn
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
2026-08-23 19:30:38 +02:00
ddidderr d80f823d4b fix(physics): restore four-sided board bounds
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
2026-08-23 19:25:17 +02:00
ddidderr f4e6a5a372 fix(av): restore original nudge and sound presentation
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
2026-08-23 19:23:24 +02:00
ddidderr c84df73857 fix(ui): restore effect and player display assets
Load and render DAT400-407 effect panels with the preserved wheel overlap, and add deterministic effect-7 coverage. Restore exact 1008:0996/0b9e score fields, configured-player rows, current/status indicators, signed status thresholds, and six 5x5 per-player ball-marker slots.

Test Plan:
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- rumdl check CHANGELOG.md RECONSTRUCTION.md README.md
- cargo run --quiet -- --simulate effect-7 --step 0 --screenshot /tmp/tdkpin-effect7.png
- cargo run --quiet -- --simulate launcher --step 0 --screenshot /tmp/tdkpin-display.png
- git diff --check
2026-08-23 19:21:28 +02:00
ddidderr 0643138c0e fix(ui): restore exact score and status displays
Render 1008:0996's eight-place 130x40 score fields at x=478 through right edge 606, exact configured-player rows, current/status markers, and all six 5x5 ball-marker slots. Derive 1008:0b9e status images from strict signed score thresholds rather than unrelated media-award state.

Test Plan:
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- rumdl check CHANGELOG.md RECONSTRUCTION.md README.md
- cargo run --quiet -- --simulate launcher --step 0 --screenshot /tmp/tdkpin-display.png
- git diff --check
2026-08-23 19:19:12 +02:00
ddidderr 0e7208e03a fix(config): import original INI semantics
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
2026-08-23 19:12:31 +02:00
ddidderr fae322bb91 fix(ui): restore original high-score windows
Replace modern overlays with the reconstructed HighScore child and DIALOG 32514 topology, exact DAT993 background, row coordinates, original strings, unpadded scores, and 21-character edit behavior. Restore OK/Cancel semantics, including Escape preserving the prefilled name and allowing an accepted empty name, with deterministic screenshot scenarios.

Test Plan:
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- rumdl check CHANGELOG.md RECONSTRUCTION.md README.md
- cargo run --quiet -- --simulate highscores --step 0 --screenshot /tmp/tdkpin-highscores.png
- cargo run --quiet -- --simulate name-entry --step 0 --screenshot /tmp/tdkpin-name-entry.png
- git diff --check
2026-08-23 19:09:28 +02:00
ddidderr c80f5e2136 fix(ui): restore staged loading progress
Correct raw 1000:531e's Pascal-order BitBlt to destination (202,328), height 13, and width (stage-1)*7 from original DAT994. Render stages 2 through 35 over DAT995, add deterministic loading screenshots, and update the C harness and evidence ledger for the corrected 237x13 source.

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
- cargo run --quiet -- --simulate loading --at 0.55 --screenshot /tmp/tdkpin-loading-mid.png
- git diff --check
2026-08-23 19:03:09 +02:00
ddidderr 8168d9dc23 fix(ui): restore original attract animation
Advance idle mode on the selected detail callback and reproduce the reconstructed target, word-quad, magnetic, record-strip, point-chase, item, and BITMAP500 marquee phases from original assets. Add a deterministic attract screenshot scenario and correct DAT801-809 item placement to y=300.

Test Plan:
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- rumdl check CHANGELOG.md RECONSTRUCTION.md README.md
- cargo run --quiet -- --simulate attract --step 24 --screenshot /tmp/tdkpin-attract-24.png
- cargo run --quiet -- --simulate attract --step 192 --screenshot /tmp/tdkpin-attract-192.png
- cargo run --quiet -- --simulate attract --step 4608 --screenshot /tmp/tdkpin-attract-4608.png
- git diff --check
2026-08-23 18:59:19 +02:00
ddidderr 2936c69c5c fix(physics): preserve first-candidate scan semantics
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
2026-08-23 18:50:20 +02:00
ddidderr f8dcceda5e fix(physics): restore binary record scan order
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
2026-08-23 18:46:44 +02:00
ddidderr 89880e9b45 fix(physics): process sensors before collision response
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
2026-08-23 18:37:03 +02:00
ddidderr 95b6994735 fix(physics): apply predicted record broadphase
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
2026-08-23 18:29:53 +02:00
ddidderr 975fb27494 refactor(physics): defer static collision responses
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
2026-08-23 18:28:02 +02:00
ddidderr 742b8b7981 fix(physics): use predicted magnetic gate bounds
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
2026-08-23 18:25:23 +02:00
ddidderr 8c51094b31 fix(timing): decay tilt on detail callbacks
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
2026-08-23 18:17:29 +02:00
ddidderr 0661665374 fix(input): restore multiball nudge slot updates
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
2026-08-23 18:16:09 +02:00
ddidderr 3d5a105c10 fix(physics): preserve record layer and kick fields
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
2026-08-23 18:13:35 +02:00
ddidderr 692e0087f9 fix(physics): restore slot-major timer batches
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
2026-08-23 18:08:21 +02:00
ddidderr 445af4a030 fix(game): dispatch sensors for both ball slots
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
2026-08-23 18:04:30 +02:00
ddidderr bee3c440bf fix(game): restore effect-seven multiball spawn
Use the exact record 148 spawn point and the reconstructed 0.8 * 3800 millipixel velocity for ball slot two. This removes the guessed shooter-lane launch and keeps the multiball lifecycle tied to the recovered effect-seven path.

Test Plan:
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- rumdl check CHANGELOG.md RECONSTRUCTION.md README.md
- git diff --check
2026-08-23 17:58:37 +02:00
ddidderr f8292dc87e fix(render): restore timed record overlays
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
2026-08-23 17:55:59 +02:00
ddidderr 096d923c79 fix(game): restore six-state target rotation
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
2026-08-23 17:53:40 +02:00
ddidderr 4b84460c93 fix(game): restore the 281-frame panel sequence
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
2026-08-23 17:49:04 +02:00
ddidderr 6337291461 fix(ui): preserve original idle view and function keys
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
2026-08-23 17:43:44 +02:00
ddidderr 2549eaa459 fix(timing): batch state on original detail callbacks
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
2026-08-23 17:41:18 +02:00
ddidderr 1f0085902c fix(physics): port the Borland Real48 collision core
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
2026-08-23 17:36:03 +02:00
ddidderr 98f21b31c0 fix(physics): restore type-three contact state
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
2026-08-23 17:18:20 +02:00
ddidderr d72db79af6 fix(physics): replace fitted flipper transfer
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
2026-08-23 17:11:48 +02:00
ddidderr c1d8e5755f fix(input): restore launcher press and release impulses
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
2026-08-23 17:03:35 +02:00
ddidderr 2f7e29b6a4 fix(physics): restore type-four trigger response
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
2026-08-23 17:01:44 +02:00
ddidderr 3fa3e29ad3 fix(scores): restore per-player high-score flow
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
2026-08-23 17:00:12 +02:00
ddidderr 64d41eb605 fix(game): preserve rule state across player turns
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
2026-08-23 16:58:10 +02:00
ddidderr 0c52100a7e fix(game): restore per-player secondary scoring
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
2026-08-23 16:56:59 +02:00
ddidderr aae4671586 fix(input): restore original nudge and tilt behavior
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
2026-08-23 16:55:02 +02:00
ddidderr c25287186a fix(game): use the original Borland random stream
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
2026-08-23 16:52:17 +02:00
ddidderr 9de3dcc755 fix(game): restore original scoring and sound dispatch
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
2026-08-23 16:48:41 +02:00
ddidderr 8b99e9607c feat(reconstruction): complete binary-backed C recovery
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
2026-08-23 16:41:17 +02:00
ddidderr aef404c834 fix(ui): restore original presentation scale
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
2026-08-23 06:08:11 +02:00
ddidderr a70331fb8c docs(reconstruction): close gameplay fidelity ledger
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
Update the evidence boundary after fixed-point contacts, complete rule/scoring
routes, magnetic fields, all claw terminals, and effect-seven multiball were
restored. Distinguish covered gameplay behavior from the remaining intentional
source/platform differences: live-fitted flipper arithmetic and Win16 repaint
batching.

Test Plan:
- final 52-test, strict Clippy, production, and cross-target suite -- passed
- 63/63 raw resources and every decoded derivative -- verified
- restored physics C strict C11 syntax -- passed
- two-minute autoplay and real-window keyboard smoke run -- passed
- `git diff --cached --check` -- passed
2026-08-22 22:04:46 +02:00
ddidderr f2560ee2f7 fix(game): restore effect-seven multiball
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
2026-08-22 22:02:50 +02:00
ddidderr 25e32b2c06 fix(game): honor original mechanism timer choices
Map detail levels 1-5 to the recovered 50, 40, 30, 20, and 10 ms callback
periods for claw animation while retaining the invariant 10 ms physics step.
This removes the hard-coded default cadence from non-default configurations.

Keep repaint batching documented separately because Rust exposes intermediate
physics states instead of reproducing Win16 GDI callback batching.

Test Plan:
- `cargo test --all-targets` -- 51 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- all five claw cadence boundary tests -- passed
- `git diff --cached --check` -- passed
2026-08-22 21:55:21 +02:00
ddidderr 77c93ec36b fix(game): restore exact media thresholds
Replace the inferred exponential media progression with the four initialized
32-bit thresholds used by the original score-addition path: 140,000, 650,000,
1,300,000, and 4,000,000. Award exactly one media level and extra ball at each,
with no nonexistent fifth tier.

Test Plan:
- `cargo test --all-targets` -- 50 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- threshold boundary and four-extra-ball test -- passed
- `git diff --cached --check` -- passed
2026-08-22 21:53:11 +02:00
ddidderr b786a75544 fix(game): restore the six-way effect target
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
2026-08-22 21:50:50 +02:00
ddidderr 27749cb5f8 fix(game): correct every claw release velocity
Interpret the claw's 3800-unit release scalar per 10 ms physics substep rather
than per 30 ms animation frame. This removes a factor-of-three slowdown from all
four random terminals and restores terminal 18 to raw `(0,1000)`.

Add exact assertions for positions and raw velocities of terminals 1, 6, 7,
and 18, backed by repeated live original captures through release and the first
three gravity substeps.

Test Plan:
- `cargo test --all-targets` -- 48 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- all four live original terminal trajectories matched
- `git diff --cached --check` -- passed
2026-08-22 21:46:07 +02:00
ddidderr 4b56a23fb4 fix(game): restore one-shot magnetic fields
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
2026-08-22 21:43:13 +02:00
ddidderr b86810ebed fix(physics): restore the flipper downstroke
Represent press and release as distinct deferred flipper edges. Keep upward
position/velocity transfer unchanged, and apply the position-preserving
return-stroke velocity recovered from a live ball at `(125,390)`, mirrored for
the right flipper.

The downstroke now assigns raw `(3519,6302)` before the next physics clamp,
matching the original instead of moving geometry silently.

Test Plan:
- `cargo test --all-targets` -- 48 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- live downstroke transfer test -- passed within one raw unit
- `git diff --cached --check` -- passed
2026-08-22 21:36:24 +02:00