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
286 lines
8.8 KiB
C
286 lines
8.8 KiB
C
/*
|
|
* 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;
|
|
} MillipixelWords;
|
|
|
|
typedef struct {
|
|
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.
|
|
*/
|
|
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 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)
|
|
{
|
|
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}};
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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_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);
|
|
claw->ball_suspended = false;
|
|
}
|