docs(reverse): restore named mechanism state machines
Transcribe the claw and flipper control flow into an address-mapped readable companion while preserving unresolved fixed-point projection as an explicit boundary. Correct the reconstruction ledger so the current Rust claw is not described as proven parity. Test Plan: - git diff --cached --check - Review restored constants and branches against TDKPIN_GHIDRA_RAW.c
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Readable mechanics transcription for TDKPIN.EXE.
|
||||
*
|
||||
* Target SHA-256:
|
||||
* a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c
|
||||
*
|
||||
* This is a named, reviewable companion to TDKPIN_GHIDRA_RAW.c. It is not
|
||||
* claimed to be the vendor's source or a buildable replacement. Every
|
||||
* restored routine below names its raw address, and unresolved conversions
|
||||
* stay explicit instead of being filled with guessed values.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum {
|
||||
SOUND_RESOURCE_BASE = 2000,
|
||||
SOUND_CLAW_CAPTURE = SOUND_RESOURCE_BASE + 15,
|
||||
SOUND_CLAW_RELEASE = SOUND_RESOURCE_BASE + 16,
|
||||
SOUND_FLIPPER_MOVE = SOUND_RESOURCE_BASE + 21,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t width;
|
||||
int16_t height;
|
||||
} RectI16;
|
||||
|
||||
typedef struct {
|
||||
bool left_control_down; /* object +0xbda; scan code 0x1d */
|
||||
bool keypad_enter_down; /* object +0xbdb; scan code 0x1c */
|
||||
int16_t left_position; /* object +0xbd4; recovered range 0..1 */
|
||||
int16_t right_position; /* object +0xbd6; recovered range 0..1 */
|
||||
} FlipperState;
|
||||
|
||||
typedef enum {
|
||||
CLAW_OPENING_OR_IDLE = 0,
|
||||
CLAW_CLOSING = 1,
|
||||
} ClawSpriteBank;
|
||||
|
||||
typedef struct {
|
||||
bool active; /* DAT_1028_07ca */
|
||||
uint16_t frame; /* DAT_1028_086d; initialized to 10 */
|
||||
uint16_t target_frame; /* DAT_1028_086f; initialized to 10 */
|
||||
ClawSpriteBank bank; /* DAT_1028_0873 */
|
||||
bool animation_blocked; /* DAT_1028_0874 */
|
||||
bool ball_suspended; /* game object +0xbdf */
|
||||
} ClawState;
|
||||
|
||||
typedef struct {
|
||||
uint16_t low;
|
||||
int16_t high;
|
||||
} Fixed32Words;
|
||||
|
||||
typedef struct {
|
||||
Fixed32Words x;
|
||||
Fixed32Words y;
|
||||
} RawClawReleasePosition;
|
||||
|
||||
/*
|
||||
* FUN_1008_0002 at 1008:0002.
|
||||
* The called loader/player resolves argument N as Win16 WAVE resource 2000+N.
|
||||
*/
|
||||
static void play_sound_number(uint16_t number)
|
||||
{
|
||||
if (sound_is_not_muted() && sound_device_is_available()) {
|
||||
play_wave_resource(SOUND_RESOURCE_BASE + number);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* FUN_1000_638e / FUN_1000_6bf8 (keyboard release/press branches).
|
||||
* The original controls are binary input flags. Motion happens later in
|
||||
* update_flippers(), once per changed edge.
|
||||
*/
|
||||
static void set_flipper_key(FlipperState *state, uint8_t scan_code, bool down)
|
||||
{
|
||||
if (scan_code == 0x1d) {
|
||||
state->left_control_down = down;
|
||||
} else if (scan_code == 0x1c) {
|
||||
state->keypad_enter_down = down;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* FUN_1000_7ed9 at 1000:7ed9 and FUN_1000_8b0d at 1000:8b0d.
|
||||
* delta is -1 for key-down and +1 for key-up. The first routine updates
|
||||
* the live collision geometry and always starts sound 2021; the second moves
|
||||
* the matching rendered flipper geometry.
|
||||
*/
|
||||
static void move_flipper(int flipper_number, int delta)
|
||||
{
|
||||
play_sound_number(21);
|
||||
update_flipper_collision_geometry(flipper_number, delta);
|
||||
update_flipper_bitmap_geometry(flipper_number, delta);
|
||||
}
|
||||
|
||||
/* FUN_1000_99f0 at 1000:99f0. */
|
||||
static void update_flippers(FlipperState *state)
|
||||
{
|
||||
if (state->left_control_down && state->left_position == 0) {
|
||||
state->left_position++;
|
||||
move_flipper(1, -1);
|
||||
} else if (!state->left_control_down && state->left_position != 0) {
|
||||
state->left_position--;
|
||||
move_flipper(1, +1);
|
||||
}
|
||||
|
||||
if (state->keypad_enter_down && state->right_position == 0) {
|
||||
state->right_position++;
|
||||
move_flipper(2, -1);
|
||||
} else if (!state->keypad_enter_down && state->right_position != 0) {
|
||||
state->right_position--;
|
||||
move_flipper(2, +1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* FUN_1008_0e08 at 1008:0e08.
|
||||
* Resource 900 is a 900x296 sheet: nine 100x74 frames in each of four rows.
|
||||
* Closing uses rows 0/74; opening and returning use rows 148/222. The
|
||||
* destination is the logical playfield rectangle x=238, y=47, w=100, h=74.
|
||||
*/
|
||||
static RectI16 claw_source_rect(uint16_t frame, ClawSpriteBank bank)
|
||||
{
|
||||
uint16_t column_frame = frame;
|
||||
int16_t source_y = bank == CLAW_CLOSING ? 0 : 148;
|
||||
|
||||
if (column_frame > 9) {
|
||||
source_y += 74;
|
||||
column_frame -= 9;
|
||||
}
|
||||
|
||||
return (RectI16){
|
||||
.x = (int16_t)((column_frame - 1) * 100),
|
||||
.y = source_y,
|
||||
.width = 100,
|
||||
.height = 74,
|
||||
};
|
||||
}
|
||||
|
||||
static void render_claw(uint16_t frame, ClawSpriteBank bank)
|
||||
{
|
||||
const RectI16 destination = {238, 47, 100, 74};
|
||||
|
||||
if (frame == 0) {
|
||||
restore_playfield_background(destination);
|
||||
} else {
|
||||
blit_claw_sprite(claw_source_rect(frame, bank), destination);
|
||||
}
|
||||
}
|
||||
|
||||
/* The only terminal frames selected by the collision branch at 1000:c79c. */
|
||||
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.
|
||||
*/
|
||||
static RawClawReleasePosition raw_claw_release_position(uint16_t frame)
|
||||
{
|
||||
switch (frame) {
|
||||
case 1:
|
||||
case 3:
|
||||
return (RawClawReleasePosition){{0xefd0, 3}, {0x30b0, 1}};
|
||||
case 2:
|
||||
return (RawClawReleasePosition){{0xf3b8, 3}, {0x3c68, 1}};
|
||||
case 4:
|
||||
return (RawClawReleasePosition){{0xfb88, 3}, {0x4ff0, 1}};
|
||||
case 5:
|
||||
return (RawClawReleasePosition){{0x0f10, 4}, {0x5f90, 1}};
|
||||
case 6:
|
||||
return (RawClawReleasePosition){{0x1eb0, 4}, {0x6f30, 1}};
|
||||
case 7:
|
||||
return (RawClawReleasePosition){{0x3238, 4}, {0x7ae8, 1}};
|
||||
case 8:
|
||||
return (RawClawReleasePosition){{0x3df0, 4}, {0x7ed0, 1}};
|
||||
case 9:
|
||||
return (RawClawReleasePosition){{0x4d90, 4}, {0x8a88, 1}};
|
||||
case 18:
|
||||
return (RawClawReleasePosition){{0xf588, 4}, {0x6760, 1}};
|
||||
default:
|
||||
unreachable_original_state();
|
||||
return (RawClawReleasePosition){{0, 0}, {0, 0}};
|
||||
}
|
||||
}
|
||||
|
||||
/* Object-id 0x59 branch inside FUN_1000_c79c at 1000:c79c. */
|
||||
static void begin_claw_capture(ClawState *claw, uint16_t random_0_to_3)
|
||||
{
|
||||
if (claw->animation_blocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
claw->active = true;
|
||||
claw->target_frame = CLAW_TERMINAL_FRAMES[random_0_to_3];
|
||||
claw->bank = CLAW_CLOSING;
|
||||
claw->ball_suspended = true;
|
||||
zero_ball_motion();
|
||||
|
||||
/* These moving collision records are absent while the arm holds the ball. */
|
||||
set_collision_objects_enabled(12, 20, false);
|
||||
play_sound_number(15); /* recovered call immediately before this branch */
|
||||
}
|
||||
|
||||
/* Claw branch of exported ordinal 10 at 1000:eab1. */
|
||||
static void update_claw(ClawState *claw)
|
||||
{
|
||||
if (!claw->active || claw->animation_blocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (claw->frame != claw->target_frame) {
|
||||
claw->frame += claw->frame < claw->target_frame ? 1 : -1;
|
||||
render_claw(claw->frame, claw->bank);
|
||||
return;
|
||||
}
|
||||
|
||||
if (claw->frame == 10) {
|
||||
render_claw(0, CLAW_OPENING_OR_IDLE);
|
||||
if (claw->bank == CLAW_OPENING_OR_IDLE) {
|
||||
claw->active = false;
|
||||
set_collision_objects_enabled(12, 20, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Terminal closing frame: redraw from the opening bank, release, return. */
|
||||
render_claw(claw->frame, CLAW_OPENING_OR_IDLE);
|
||||
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 */
|
||||
}
|
||||
|
||||
commit_ball_position();
|
||||
play_sound_number(16);
|
||||
claw->ball_suspended = false;
|
||||
}
|
||||
Reference in New Issue
Block a user