Files
tdkpin/original/TDKPIN_PHYSICS_RESTORED.c
T
ddidderr 8b99e9607c feat(reconstruction): complete binary-backed C recovery
Replace the partial mechanics transcriptions with a separate, readable C11
reconstruction of the complete Win16 image while preserving the original raw
Ghidra export as immutable evidence. Cover all ordinary and overlapping entry
points, Borland runtime behavior, Win16 imports, segmented data, callbacks,
resources, indirect control flow, physics, rendering, persistence, and
startup/shutdown lifecycles.

Add deterministic extraction and audit tooling plus address-linked ledgers for
functions, imports, DGROUP ranges and objects, relocations, resources, and
callbacks. The final gate records zero raw, partial, restored, unknown,
blocked, or unclassified required units. Keep the semantic-fidelity boundary
explicit: the portable C is not claimed to reproduce a byte-identical Borland
NE build.

Add strict focused harnesses for every reconstructed C unit, exact resource
round-trip checks, and a 16-bit Borland Real48 reference probe. No Rust source
or Cargo metadata is changed in this phase.

Test Plan:
- `bash original/tools/test_reconstructed_c.sh` -- passed
- `bash original/tools/probe_real48_reference.sh` -- passed bit-for-bit
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `git diff --cached --check` -- passed
- `git diff HEAD -- '*.rs' Cargo.toml Cargo.lock` -- empty
2026-08-23 16:41:17 +02:00

291 lines
9.8 KiB
C

/*
* HISTORICAL PARTIAL 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 superseded intermediate is retained only as provenance for earlier
* static/live comparison work and is not part of the completed deliverable.
* The complete 1000:c79c implementation is reconstructed/tdkpin_physics.c;
* its rule and persistent-contact dependencies are likewise represented by
* address-linked verified modules under reconstructed/.
*/
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
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,
};