docs(reverse): decode claw release arithmetic
Correct the coordinate model from guessed 16.16 values to the millipixel arithmetic shown by the 16-bit division routine. Decode the Borland Real48 direction constants and record the exact position and velocity tables used by every claw terminal. Test Plan: - git diff --cached --check - Compare constants against segment 1 disassembly at 9bca and eab1
This commit is contained in:
@@ -42,17 +42,22 @@ renamed.
|
||||
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.
|
||||
|
||||
## Explicit pending evidence
|
||||
|
||||
- Decode the runtime/FPU conversion used to project the claw's raw 16.16
|
||||
release coordinates and non-frame-18 velocity into playfield pixels.
|
||||
- Recover or differentially measure the timer cadence used for each claw frame.
|
||||
- Differentially verify the recovered 10/20/30/40/50 ms timer choices against
|
||||
live original captures; the Rust port intentionally uses the default 30 ms
|
||||
claw cadence independently of render rate.
|
||||
- Transcribe the full fixed-point flipper impulse calculation, then compare it
|
||||
with the Rust floating-point collision response.
|
||||
- Validate the reconstructed sequences against deterministic Rust traces and
|
||||
captured frames from the original executable.
|
||||
|
||||
Those four items are intentionally not counted as semantic parity. The readable
|
||||
Those items are intentionally not counted as semantic parity. The readable
|
||||
C file preserves their raw constants and labels the unresolved conversion so a
|
||||
later implementation cannot silently turn an estimate into claimed evidence.
|
||||
|
||||
@@ -51,13 +51,18 @@ typedef struct {
|
||||
typedef struct {
|
||||
uint16_t low;
|
||||
int16_t high;
|
||||
} Fixed32Words;
|
||||
} MillipixelWords;
|
||||
|
||||
typedef struct {
|
||||
Fixed32Words x;
|
||||
Fixed32Words y;
|
||||
MillipixelWords x;
|
||||
MillipixelWords y;
|
||||
} RawClawReleasePosition;
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} Vec2F;
|
||||
|
||||
/*
|
||||
* FUN_1008_0002 at 1008:0002.
|
||||
* The called loader/player resolves argument N as Win16 WAVE resource 2000+N.
|
||||
@@ -154,9 +159,10 @@ static void render_claw(uint16_t frame, ClawSpriteBank bank)
|
||||
static const uint16_t CLAW_TERMINAL_FRAMES[4] = {1, 6, 7, 18};
|
||||
|
||||
/*
|
||||
* Raw 16.16 positions assigned by export ordinal 10 at 1000:eab1.
|
||||
* These words are preserved until the original coordinate projection is
|
||||
* fully decoded; do not substitute guessed screen pixels here.
|
||||
* Raw signed 32-bit millipixel positions assigned by export ordinal 10 at
|
||||
* 1000:eab1. FUN_1000_9bca adds 500, divides by 1000, then subtracts the
|
||||
* eight-pixel ball radius only for bitmap placement. The physics position is
|
||||
* therefore the raw value divided by 1000, not a 16.16 fixed-point number.
|
||||
*/
|
||||
static RawClawReleasePosition raw_claw_release_position(uint16_t frame)
|
||||
{
|
||||
@@ -186,6 +192,44 @@ static RawClawReleasePosition raw_claw_release_position(uint16_t frame)
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t join_millipixel_words(MillipixelWords value)
|
||||
{
|
||||
return ((int32_t)value.high << 16) | value.low;
|
||||
}
|
||||
|
||||
static Vec2F claw_release_position_pixels(uint16_t frame)
|
||||
{
|
||||
RawClawReleasePosition raw = raw_claw_release_position(frame);
|
||||
return (Vec2F){
|
||||
join_millipixel_words(raw.x) / 1000.0f,
|
||||
join_millipixel_words(raw.y) / 1000.0f,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Real48 constants loaded in 1000:eab1 after converting speed scalar 3800.
|
||||
* The result is millipixels per timer tick. Only 1, 6, 7, and 18 are selected
|
||||
* as random terminal frames, but all switch cases are retained here.
|
||||
*/
|
||||
static Vec2F claw_release_velocity_millipixels_per_tick(uint16_t frame)
|
||||
{
|
||||
switch (frame) {
|
||||
case 1: return (Vec2F){3800.0f * -0.60f, 3800.0f * 0.00f};
|
||||
case 2: return (Vec2F){3800.0f * -0.45f, 3800.0f * 0.05f};
|
||||
case 3: return (Vec2F){3800.0f * -0.70f, 3800.0f * 0.30f};
|
||||
case 4: return (Vec2F){3800.0f * -0.30f, 3800.0f * 0.20f};
|
||||
case 5: return (Vec2F){3800.0f * -0.32f, 3800.0f * 0.22f};
|
||||
case 6: return (Vec2F){3800.0f * -0.40f, 3800.0f * 0.60f};
|
||||
case 7: return (Vec2F){3800.0f * -0.31f, 3800.0f * 0.70f};
|
||||
case 8: return (Vec2F){3800.0f * -0.20f, 3800.0f * 0.80f};
|
||||
case 9: return (Vec2F){3800.0f * -0.10f, 3800.0f * 0.90f};
|
||||
case 18: return (Vec2F){0.0f, 1000.0f};
|
||||
default:
|
||||
unreachable_original_state();
|
||||
return (Vec2F){0.0f, 0.0f};
|
||||
}
|
||||
}
|
||||
|
||||
/* Object-id 0x59 branch inside FUN_1000_c79c at 1000:c79c. */
|
||||
static void begin_claw_capture(ClawState *claw, uint16_t random_0_to_3)
|
||||
{
|
||||
@@ -231,13 +275,9 @@ static void update_claw(ClawState *claw)
|
||||
claw->target_frame = 10;
|
||||
claw->bank = CLAW_OPENING_OR_IDLE;
|
||||
reset_ball_forces();
|
||||
set_ball_position_raw(raw_claw_release_position(claw->frame));
|
||||
|
||||
if (claw->frame == 18) {
|
||||
set_ball_velocity_raw(0, 1000);
|
||||
} else {
|
||||
set_ball_velocity_from_original_speed_scalar(); /* projection pending */
|
||||
}
|
||||
set_ball_position_pixels(claw_release_position_pixels(claw->frame));
|
||||
set_ball_velocity_millipixels_per_tick(
|
||||
claw_release_velocity_millipixels_per_tick(claw->frame));
|
||||
|
||||
commit_ball_position();
|
||||
play_sound_number(16);
|
||||
|
||||
Reference in New Issue
Block a user