From cd27fc8404e15985739c0f307afd6113428005d1 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 22 Aug 2026 21:29:41 +0200 Subject: [PATCH] docs(reverse): transcribe the fixed-point physics core Add a standalone, syntax-checked C transcription of the recovered ball state, 10 ms substep, launcher repeat/release arithmetic, one-sided type-2 response, swept type-1 response, and previous-position contact application. Link the source to the authoritative raw function addresses and initialized object ledger. Keep unresolved persistent-contact and special-rule branches in the raw Ghidra artifact rather than filling the readable reference with guesses. Test Plan: - strict C11 `clang -Wall -Wextra -Werror -fsyntax-only` -- passed - `git diff --cached --check` -- passed --- original/DECOMPILATION.md | 2 + original/TDKPIN_PHYSICS_RESTORED.c | 289 +++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 original/TDKPIN_PHYSICS_RESTORED.c diff --git a/original/DECOMPILATION.md b/original/DECOMPILATION.md index 7639f37..bf6c91a 100644 --- a/original/DECOMPILATION.md +++ b/original/DECOMPILATION.md @@ -16,6 +16,8 @@ the output are therefore analysis-generated identifiers. ## Outputs - `TDKPIN_GHIDRA_RAW.c`: C-like Ghidra output for every recovered in-image function. +- `TDKPIN_PHYSICS_RESTORED.c`: syntax-checked, address-linked transcription of + the fixed-point state and recovered type-1/type-2 response arithmetic. - `FUNCTIONS.tsv`: address ledger separating in-image functions from imported APIs. - `COVERAGE.tsv`: executable-block, instruction, function-body, and undefined-range audit. - `OBJECTS.tsv`: all 175 initialized collision/rule records, including geometry, diff --git a/original/TDKPIN_PHYSICS_RESTORED.c b/original/TDKPIN_PHYSICS_RESTORED.c new file mode 100644 index 0000000..2e76bad --- /dev/null +++ b/original/TDKPIN_PHYSICS_RESTORED.c @@ -0,0 +1,289 @@ +/* + * Readable fixed-point physics transcription for TDKPIN.EXE. + * + * Authority: + * SHA-256 a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c + * + * Primary raw routine: + * FUN_1000_c79c at 1000:c79c (movement, object scan, response dispatch) + * + * Supporting routines: + * FUN_1000_638e / FUN_1000_6bf8 (launcher key release/press) + * FUN_1000_7ed9 / FUN_1000_8b0d (moving flipper records) + * FUN_1000_b476 (rule flags) + * + * This file transcribes behavior that has both static and live differential + * evidence. It is not claimed to be the vendor's source. Unrestored rule and + * persistent-contact branches remain in TDKPIN_GHIDRA_RAW.c rather than being + * replaced here with plausible code. + */ + +#include +#include +#include +#include + +enum { + PHYSICS_SUBSTEP_MILLISECONDS = 10, + GRAVITY_MILLIPIXELS_PER_SUBSTEP = 15, + MAX_SPEED_MILLIPIXELS_PER_SUBSTEP = 3800, + LAUNCHER_IMPULSE_MILLIPIXELS = 375, + COLLISION_OBJECT_COUNT = 175, +}; + +typedef struct { + int32_t x; + int32_t y; +} Vec2Milli; + +typedef enum { + OBJECT_TYPE_CIRCLE = 1, + OBJECT_TYPE_LINE = 2, + OBJECT_TYPE_LOCK_OR_MECHANISM = 3, + OBJECT_TYPE_TARGET_SENSOR = 4, +} CollisionObjectType; + +/* Semantic view of one initialized 0x53-byte record from OBJECTS.tsv. */ +typedef struct { + uint8_t id; + CollisionObjectType type; /* record +0x00 relative to field base 0x095b */ + uint8_t subtype; /* +0x01 */ + bool active; /* +0x34 */ + Vec2Milli bounds_min; /* +0x02 / +0x06 */ + Vec2Milli bounds_max; /* +0x0a / +0x0e */ + Vec2Milli point1; /* +0x12 / +0x16 */ + Vec2Milli point2; /* +0x1a / +0x1e */ + int32_t radius; /* Real48 at +0x22, decoded to millipixels */ + double normal_rebound; /* Real48 at +0x28 */ + double tangent_coupling; /* Real48 at +0x2e */ + double auxiliary; /* Real48 at +0x35 */ + double normal_kick; /* Real48 at +0x3b */ + uint16_t flags; /* +0x41 */ + uint16_t contact_state; /* +0x43 */ + uint32_t score; /* +0x45 / +0x47 */ + uint16_t layer_mask; /* +0x49 */ +} CollisionRecord; + +typedef struct { + Vec2Milli position; /* DAT_1028_07d3 / DAT_1028_07d7 */ + Vec2Milli predicted; /* DAT_1028_07db / DAT_1028_07df */ + Vec2Milli velocity; /* window object +0xbaa / +0xbae */ + Vec2Milli previous_position; /* window object +0xbba / +0xbbe */ +} BallPhysics; + +typedef struct { + bool hit; + double progress; + Vec2Milli response_velocity; + uint8_t object_id; +} CollisionCandidate; + +static int64_t cross(Vec2Milli left, Vec2Milli right) +{ + return (int64_t)left.x * right.y - (int64_t)left.y * right.x; +} + +static Vec2Milli subtract(Vec2Milli left, Vec2Milli right) +{ + return (Vec2Milli){left.x - right.x, left.y - right.y}; +} + +static Vec2Milli add(Vec2Milli left, Vec2Milli right) +{ + return (Vec2Milli){left.x + right.x, left.y + right.y}; +} + +/* Type-2 branch inside 1000:c79c. The registered direction is significant. */ +static CollisionCandidate line_candidate( + Vec2Milli old_position, + Vec2Milli velocity, + const CollisionRecord *record) +{ + Vec2Milli line = subtract(record->point2, record->point1); + Vec2Milli from_ball = subtract(record->point1, old_position); + int64_t denominator = cross(velocity, line); + int64_t path_numerator; + int64_t line_numerator; + double length; + double tangent_x; + double tangent_y; + double normal_x; + double normal_y; + double normal_speed; + double tangent_speed; + double outgoing_normal; + double outgoing_tangent; + + if (denominator == 0) { + return (CollisionCandidate){0}; + } + path_numerator = cross(from_ball, line); + line_numerator = cross(from_ball, velocity); + if (denominator > 0) { + if (path_numerator <= 0 || path_numerator > denominator || + line_numerator < 0 || line_numerator > denominator) { + return (CollisionCandidate){0}; + } + } else if (path_numerator >= 0 || path_numerator < denominator || + line_numerator > 0 || line_numerator < denominator) { + return (CollisionCandidate){0}; + } + + length = hypot((double)line.x, (double)line.y); + if (length == 0.0) { + return (CollisionCandidate){0}; + } + tangent_x = line.x / length; + tangent_y = line.y / length; + normal_x = -tangent_y; + normal_y = tangent_x; + normal_speed = velocity.x * normal_x + velocity.y * normal_y; + if (normal_speed <= 0.0) { /* back side is a one-way pass */ + return (CollisionCandidate){0}; + } + tangent_speed = velocity.x * tangent_x + velocity.y * tangent_y; + outgoing_normal = -record->normal_rebound * normal_speed; + outgoing_tangent = tangent_speed + record->tangent_coupling * normal_speed; + return (CollisionCandidate){ + .hit = true, + .progress = (double)path_numerator / (double)denominator, + .response_velocity = { + (int32_t)llround(normal_x * outgoing_normal + tangent_x * outgoing_tangent), + (int32_t)llround(normal_y * outgoing_normal + tangent_y * outgoing_tangent), + }, + .object_id = record->id, + }; +} + +/* Type-1 branch inside 1000:c79c. */ +static CollisionCandidate circle_candidate( + Vec2Milli old_position, + Vec2Milli velocity, + const CollisionRecord *record) +{ + Vec2Milli offset = subtract(old_position, record->point1); + int64_t old_distance_squared = + (int64_t)offset.x * offset.x + (int64_t)offset.y * offset.y; + int64_t radius_squared = (int64_t)record->radius * record->radius; + double a; + double b; + double c; + double discriminant; + double progress; + double hit_x; + double hit_y; + double hit_length; + double normal_x; + double normal_y; + double tangent_x; + double tangent_y; + double normal_speed; + double tangent_speed; + double outgoing_normal; + double outgoing_tangent; + + if (old_distance_squared <= radius_squared) { + return (CollisionCandidate){0}; /* persistent state suppresses repeats */ + } + a = (double)velocity.x * velocity.x + (double)velocity.y * velocity.y; + if (a == 0.0) { + return (CollisionCandidate){0}; + } + b = 2.0 * ((double)offset.x * velocity.x + (double)offset.y * velocity.y); + c = (double)old_distance_squared - (double)radius_squared; + discriminant = b * b - 4.0 * a * c; + if (discriminant < 0.0) { + return (CollisionCandidate){0}; + } + progress = (-b - sqrt(discriminant)) / (2.0 * a); + if (progress <= 0.0 || progress > 1.0) { + return (CollisionCandidate){0}; + } + hit_x = offset.x + velocity.x * progress; + hit_y = offset.y + velocity.y * progress; + hit_length = hypot(hit_x, hit_y); + if (hit_length == 0.0) { + return (CollisionCandidate){0}; + } + normal_x = hit_x / hit_length; + normal_y = hit_y / hit_length; + tangent_x = normal_y; /* clockwise tangent */ + tangent_y = -normal_x; + normal_speed = velocity.x * normal_x + velocity.y * normal_y; + if (normal_speed >= 0.0) { + return (CollisionCandidate){0}; + } + tangent_speed = velocity.x * tangent_x + velocity.y * tangent_y; + outgoing_normal = -record->normal_rebound * normal_speed + + record->normal_kick * MAX_SPEED_MILLIPIXELS_PER_SUBSTEP; + outgoing_tangent = tangent_speed - record->tangent_coupling * normal_speed; + return (CollisionCandidate){ + .hit = true, + .progress = progress, + .response_velocity = { + (int32_t)llround(normal_x * outgoing_normal + tangent_x * outgoing_tangent), + (int32_t)llround(normal_y * outgoing_normal + tangent_y * outgoing_tangent), + }, + .object_id = record->id, + }; +} + +static void begin_substep(BallPhysics *ball) +{ + double speed; + + ball->velocity.y += GRAVITY_MILLIPIXELS_PER_SUBSTEP; + speed = hypot((double)ball->velocity.x, (double)ball->velocity.y); + if (speed > MAX_SPEED_MILLIPIXELS_PER_SUBSTEP) { + double scale = MAX_SPEED_MILLIPIXELS_PER_SUBSTEP / speed; + ball->velocity.x = (int32_t)llround(ball->velocity.x * scale); + ball->velocity.y = (int32_t)llround(ball->velocity.y * scale); + } + ball->previous_position = ball->position; + ball->predicted = add(ball->position, ball->velocity); +} + +static void apply_candidate(BallPhysics *ball, CollisionCandidate candidate) +{ + if (candidate.hit) { + ball->velocity = candidate.response_velocity; + } + /* The original advances from the previous position, not the hit point. */ + ball->position = add(ball->previous_position, ball->velocity); +} + +/* Keyboard-repeat behavior recovered from 1000:6bf8 and 1000:638e. */ +static void launcher_down_event(BallPhysics *ball) +{ + ball->velocity.x = 0; + ball->velocity.y -= LAUNCHER_IMPULSE_MILLIPIXELS; +} + +/* random_less_than_3800 is Borland random(3800), FUN_1020_14a4. */ +static void launcher_release_event(BallPhysics *ball, uint16_t random_less_than_3800) +{ + launcher_down_event(ball); /* release applies one additional impulse */ + if (ball->velocity.y < -MAX_SPEED_MILLIPIXELS_PER_SUBSTEP) { + ball->velocity.y = -MAX_SPEED_MILLIPIXELS_PER_SUBSTEP + + random_less_than_3800 / 40; + } +} + +/* Keep pure helpers referenced for strict syntax/warning checks. */ +typedef struct { + CollisionCandidate (*line)(Vec2Milli, Vec2Milli, const CollisionRecord *); + CollisionCandidate (*circle)(Vec2Milli, Vec2Milli, const CollisionRecord *); + void (*begin)(BallPhysics *); + void (*apply)(BallPhysics *, CollisionCandidate); + void (*launcher_down)(BallPhysics *); + void (*launcher_release)(BallPhysics *, uint16_t); +} RestoredPhysicsFunctions; + +const RestoredPhysicsFunctions TDKPIN_RESTORED_PHYSICS = { + line_candidate, + circle_candidate, + begin_substep, + apply_candidate, + launcher_down_event, + launcher_release_event, +};