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
209 lines
12 KiB
Markdown
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.
|