Files
tdkpin/original/MECHANICS_PROGRESS.md
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

209 lines
12 KiB
Markdown

# Historical mechanics reconstruction notes
This file preserves the live-Wine observations from the earlier mechanics-only
pass. Its former partial-status table and pending list are superseded by
`RECONSTRUCTION_PROGRESS.md` and `FUNCTION_RECONSTRUCTION.tsv`: all functions
named below, including the complete `1000:7ed9`, `1000:8b0d`, `1000:c79c`, and
`1000:eab1` paths, now have verified readable-C reconstructions. The runtime
captures below remain useful independent evidence.
This ledger scopes the current reverse-engineering pass to the claw and
flippers. The preserved executable is the authority; `TDKPIN_GHIDRA_RAW.c`
remains immutable raw evidence, while `TDKPIN_MECHANICS_RESTORED.c` carries
reviewed names and control flow.
Target: `TDKPIN.EXE` SHA-256
`a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c`.
## Function inventory
| Address | Raw name | Restored role | Status |
| --- | --- | --- | --- |
| `1000:638e` | `FUN_1000_638e` | key-release flag updates | reviewed |
| `1000:6bf8` | `FUN_1000_6bf8` | key-press flag updates | reviewed |
| `1000:7ed9` | `FUN_1000_7ed9` | flipper collision movement and sound | reviewed; inner fixed-point geometry still raw |
| `1000:8b0d` | `FUN_1000_8b0d` | flipper bitmap/collider movement | reviewed; inner fixed-point geometry still raw |
| `1000:99f0` | `FUN_1000_99f0` | binary flipper transition state machine | restored |
| `1000:c79c` | `FUN_1000_c79c` | collision dispatch, including claw object `0x59` | claw branch restored |
| `1000:eab1` | exported ordinal 10 | timer tick, claw release and return | claw branch restored |
| `1008:0002` | `FUN_1008_0002` | sound-enabled dispatch | restored |
| `1008:0e08` | `FUN_1008_0e08` | claw background restore and sprite slicing | restored |
Inventory coverage is 9/9 functions inspected for this mechanism pass. This is
not a claim that all instructions in the two large physics functions have been
renamed.
## Proven behavior now available to the Rust port
- Left Control (scan `0x1d`) and keypad Enter (scan `0x1c`) set input flags;
the timer changes each flipper position only between 0 and 1.
- Every flipper movement edge, including return, invokes sound number 21,
Win16 WAVE resource 2021.
- Claw collision object `0x59` suspends the ball, zeros its live motion,
disables collision records 12 through 20, and selects terminal frame 1, 6,
7, or 18.
- The claw advances exactly one frame per timer tick to its terminal, releases
the ball, then advances one frame per tick back to frame 10 before clearing
the overlay and restoring collision records 12 through 20.
- Resource 900 is sliced into 100x74 frames, nine columns by four rows. The
destination is `(238,47)`; closing rows begin at source y=0 and opening rows
at source y=148.
- Claw capture/contact uses sound 2015; release uses sound 2016.
- The release-coordinate projection is `(raw + 500) / 1000` for rendering;
decoded physics centers are `(258,78)`, `(270,94)`, `(275,97)`, and
`(325,92)` for the four selectable terminal frames.
- The speed scalar is 3800 millipixels per tick. Its Borland Real48 direction
constants are decoded for all release cases in the readable C companion.
## Evidence that was pending in this historical pass
- The full raw fixed-point flipper impulse calculation is now represented in
`reconstructed/tdkpin_flipper_collision.c` and verified at `1000:7ed9`.
- Original callback batching for detail settings 1..5 is now represented in
`reconstructed/tdkpin_timer_tick.c` and `tdkpin_physics.c`. Any later Rust
transfer remains a separate goal and is not part of this C phase.
Those items were intentionally excluded from parity at the time of these notes;
their later C closure is recorded in the address-linked main ledger.
## Runtime comparison on 2026-08-22
Wine 11.15 (staging) ran the preserved 16-bit NE executable from this tree.
Keypad `+` was sent to the real window and produced the one-player started
state. Down was then held for 1.1 seconds and released; captures confirmed that
the original plunger advances while held and launches only on release.
The Rust validation mode captured the corresponding started and half-charge
states plus closing and release frames for claw terminal 6. That original live
session did not reach the claw; the controlled tracing described below later
captured an original terminal-18 sequence.
## Live fixed-point tracing
Wine's remote debugger changes the Win16 exception path and is not suitable for
continuous gameplay traces. On Linux, `tools/run_traceable_original.sh` starts
the unmodified executable with a same-user ptrace opt-in. While it is waiting at
the launcher, `tools/trace_original_state.py` locates the live DGROUP and game
object by recovered binary/state signatures and records their fixed-point state:
```sh
original/tools/run_traceable_original.sh
original/tools/trace_original_state.py --duration 5 --output /tmp/launch.csv
```
The sampler records original millipixel positions, predicted positions,
velocities, flipper inputs and states, and claw animation state whenever any of
them changes. It neither patches `TDKPIN.EXE` nor pauses Wine. The tracing helper
is Linux-only development infrastructure; it is not part of the Rust replica.
`tools/inject_original_state.py` updates all recovered copies of the ball
position and velocity between the original 30 ms callbacks. This permits
repeatable probes at individual walls, circles, gates, and mechanism triggers:
```sh
original/tools/inject_original_state.py --x 289 --y 94 --vx 0 --vy 0
```
The executable image remains unchanged; only the current disposable Wine
process is modified. Run the injector while the ball is waiting in the launcher
so it can identify the live game object, or supply the previously reported
`--object-base` to both the injector and tracer during a sequence of active
probes. `--substeps 1` isolates one collision integration step; omitting it
retains the original configured detail level. `--stop-process` is available for
single probes, but repeated whole-process stops can strand Wine while a Win16
mutex is held and therefore are not the default.
A probe at `(289,94)` captured the original terminal-18 path. The collision
suspended the ball while frames advanced from 10 through 18 at the configured
30 ms cadence. Release assigned `(325000,92000)` and `(0,1000)` millipixels,
then cleared suspension during the same timer callback. The three default
physics substeps each added 15 millipixels of vertical velocity, confirming the
observed total gravity increment of 45 per timer tick.
Repeated clean trigger probes subsequently covered terminals 1, 6, and 7 as
well. After the three default substeps their live states were respectively
`(251160,78090),(-2280,45)`, `(265440,100930),(-1520,2325)`, and
`(271466,105070),(-1178,2705)`. Terminal 18 produced
`(325000,95090),(0,1045)`. These match the recovered release table followed by
three `+15` gravity increments; Rust tests now assert every raw release velocity.
A one-substep probe against the vertical shooter walls recovered the type-2
response arithmetic. For unit tangent `t` from the registered start to end and
left normal `n`, an incoming contact with `vn = dot(v,n)` and `vt = dot(v,t)`
uses `vn' = -0.6 * vn` and `vt' = vt + 0.1 * vn` for the ordinary rail
coefficients stored in its two Real48 fields. The new position is the previous
position plus this response velocity; the original does not separate a circle
from the line with a modern capsule solver.
Fresh one-substep probes recovered type-1 response in the radial basis. With
outward normal `n`, clockwise tangent `t`, entering speed `vn < 0`, and tangent
speed `vt`, ordinary object 155 produced `vn' = -0.6 * vn` and
`vt' = vt - 0.1 * vn`. Bumper 51 additionally added `0.4 * 3800` along `n`.
The exact captured transitions were `(0,-940) -> (94,564)` and
`(0,-1970) -> (197,3096)` millipixels per substep.
Live key edges recovered all moving flipper geometry. The raised left records
are line 66 `(98,382)->(131,369)`, circle 67 `(133,377)`, and line 68
`(137,384)->(116,405)`. The raised right records are line 81
`(197,405)->(171,378)`, circle 82 `(181,376)`, and line 83
`(183,368)->(217,383)`. Resting records retain their registration-table
coordinates. Edge-time position and velocity transfer is tracked separately
from the record geometry. Three live left-edge probes at `(115,390)`,
`(120,390)`, and `(125,390)` produced post-edge positions `(128.718,365)`,
`(133.397,360)`, and `(138.182,355)` with raw velocities `(1932,-3523)`,
`(2079,-4658)`, and `(2217,-5889)`. The Rust upward stroke fits those states;
the full raw calculation was still pending in this historical pass and is now
closed by the verified `1000:7ed9` reconstruction. A separate downstroke probe at
`(125,390)` kept position unchanged and assigned `(3519,6302)` before the next
substep clamped speed to 3800; the Rust return edge now matches that state.
The type-4 rule dispatcher at `1000:b476` shows that records 150-152 are three
independent 500-point sensors. Each sets its own ten-callback timer and enables
the associated magnetic gate; there is no recovered 1,500-per-target plus
5,000 completion rule. Records 140-147 carry their exact 1,000 through 3,000
scores and use the same enter-once contact bit, which clears after the ball
leaves their radius.
Post-audit raw-stack correction: the collision scan at `1000:c79c` stores each
later, nearer candidate in the next 0x34-byte stack slot, but response code
continues to read the first slot at `SS:...d8a2`. The first detected object ID
therefore wins even when a later candidate has smaller surface distance. The
readable C harness and Rust scanner preserve this binary quirk.
`1000:b476` decodes the physical bank flags in `OBJECTS.tsv`. Flags `0x208a`
on records 90-104 deactivate five complete three-line groups; clearing all five
raises the bumper value and rearms them. Flags `0x2092` on records 109, 112,
115, and 118 deactivate four three-line groups; clearing all four advances one
of the nine TDK diamond segments, awards the recovered segment value, and
rearms records 109-120.
Controlled type-3 probes filled records 129-133 in order. Their bonus sequence
was 10,000, 30,000, 70,000, and 150,000; the fifth hole transferred the full
310,000 to score and cleared bonus. Each hole returned the ball to `(325,413)`
without consuming it and retained contact state 99. Entering record 148 then
cleared all five hole states, confirming its wheel-reset role.
Top type-4 targets activate persistent field records rather than a shared timer:
150 enables 153 (left), 151 enables 6 (center), and 152 enables 154 (right).
Entering active record 153 at `(15,350)` changed `(0,2015)` to `(0,-3039)`
millipixels per substep in the live original. The field continued pulling upward
inside its registered bounds and deactivated after the ball exited above them.
Collision with flagged line 84 selected effect 3 in a live probe, set the
per-player effect word, and activated type-4 record 149. `1000:c4e1` maps effects
1-5 to 10,000, 20,000, 50,000, 100,000, and 200,000 bonus; effect 6 transfers
the accumulated bonus to score. Entering record 149 also awards its static 500,
clears the selection, and disables the record until line 84 is hit again.
After record 148 sets `DAT_1028_399a`, the next flagged line-84 selection uses
effect 7 instead of random 1-6. The effect-7 branch in `1000:c4e1` schedules the
timer's second-ball path and raises the active-ball count to two. The Rust port
now keeps and renders a second live ball, applies recovered physical collisions
and scores to it, and removes only the drained ball while multiball is active.
The score-addition path at `1000:bc36` indexes the initialized 32-bit threshold
table at DGROUP `0x825` using media state 1-4. The exact thresholds are 140,000,
650,000, 1,300,000, and 4,000,000. Crossing each threshold increments the media
state and remaining-ball count; the apparent fifth table word is the next
unrelated global, not a fifth media tier.