Files
tdkpin/original/reconstructed/tdkpin_flipper_collision.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

495 lines
17 KiB
C

/* Moving-flipper collision response at 1000:7ed9. */
#include "tdkpin_flippers.h"
#include "tdkpin_arithmetic.h"
#include "tdkpin_collision_records.h"
#include "tdkpin_gameplay_helpers.h"
#include "tdkpin_real48.h"
#include "tdkpin_sound.h"
enum {
COLLISION_RECORDS_BASE = 0x095b,
COLLISION_RECORD_BYTES = 0x53,
RECORD_RADIUS = 0x22,
RECORD_RESPONSE_NORMAL = 0x28,
COLLISION_SEARCH_RADIUS = 54000,
COLLISION_RESPONSE_RADIUS = 44000,
};
typedef struct {
int32_t radius;
int32_t pivot_x;
int32_t pivot_y;
int32_t unused_negative_point_x;
int32_t unused_positive_point_x;
int32_t negative_edge_x;
int32_t negative_edge_y;
int32_t positive_edge_x;
int32_t positive_edge_y;
uint16_t response_record;
} FlipperCollisionGeometry;
static const BorlandReal48 g_real48_one_tenth =
{{0x7d, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c}};
static const BorlandReal48 g_real48_one_half =
{{0x80, 0, 0, 0, 0, 0}};
static const BorlandReal48 g_real48_two =
{{0x82, 0, 0, 0, 0, 0}};
static const BorlandReal48 g_real48_two_fifths =
{{0x7f, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c}};
static const BorlandReal48 g_real48_one =
{{0x81, 0, 0, 0, 0, 0}};
static const BorlandReal48 g_real48_thousand =
{{0x8a, 0, 0, 0, 0, 0x7a}};
static Win16FarPtr caller_receiver(uint16_t caller_bp)
{
uint16_t offset = win16_read_stack_u16((uint16_t)(caller_bp + 6));
uint16_t selector = win16_read_stack_u16((uint16_t)(caller_bp + 8));
return win16_make_far_pointer(selector, offset);
}
static Win16FarPtr collision_record(uint16_t id)
{
return win16_dgroup_pointer((uint16_t)(
COLLISION_RECORDS_BASE + id * COLLISION_RECORD_BYTES));
}
static int32_t record_i32(uint16_t id, uint16_t offset)
{
return (int32_t)win16_read_u32(
win16_far_add_offset(collision_record(id), offset));
}
static BorlandReal48 record_real48(uint16_t id, uint16_t offset)
{
BorlandReal48 value;
Win16FarPtr source = win16_far_add_offset(collision_record(id), offset);
for (uint16_t index = 0; index < sizeof(value.bytes); index++) {
value.bytes[index] = win16_read_u8(
win16_far_add_offset(source, index));
}
return value;
}
static int32_t add_wrap_i32(int32_t left, int32_t right)
{
return (int32_t)((uint32_t)left + (uint32_t)right);
}
static int32_t subtract_wrap_i32(int32_t left, int32_t right)
{
return (int32_t)((uint32_t)left - (uint32_t)right);
}
static int32_t negate_wrap_i32(int32_t value)
{
return (int32_t)(0u - (uint32_t)value);
}
static int32_t absolute_wrap_i32(int32_t value)
{
return value < 0 ? negate_wrap_i32(value) : value;
}
static int32_t divide_by_thousand(int32_t value)
{
return borland_divide_i32(value, 1000).quotient;
}
static int32_t indeterminate_i32(void)
{
uint32_t low = win16_indeterminate_u16();
uint32_t high = win16_indeterminate_u16();
return (int32_t)(low | (high << 16));
}
static BorlandReal48 negate_real48(BorlandReal48 value)
{
if (value.bytes[0] != 0) {
value.bytes[5] ^= 0x80;
}
return value;
}
static FlipperCollisionGeometry load_flipper_geometry(
int32_t delta, uint16_t flipper)
{
FlipperCollisionGeometry geometry;
if (flipper == 1) {
geometry.radius = borland_real48_round_to_i32(
record_real48(65, RECORD_RADIUS));
geometry.pivot_x = subtract_wrap_i32(
record_i32(65, offsetof(TdkpinCollisionRecord, point1_x_milli)),
10000);
geometry.pivot_y = record_i32(
65, offsetof(TdkpinCollisionRecord, point1_y_milli));
geometry.unused_negative_point_x = record_i32(
66, offsetof(TdkpinCollisionRecord, point1_x_milli));
geometry.negative_edge_x = add_wrap_i32(
record_i32(66, offsetof(TdkpinCollisionRecord, point2_x_milli)),
20000);
geometry.negative_edge_y = record_i32(
66, offsetof(TdkpinCollisionRecord, point2_y_milli));
geometry.unused_positive_point_x = record_i32(
68, offsetof(TdkpinCollisionRecord, point2_x_milli));
geometry.positive_edge_x = add_wrap_i32(
record_i32(68, offsetof(TdkpinCollisionRecord, point1_x_milli)),
20000);
geometry.positive_edge_y = record_i32(
68, offsetof(TdkpinCollisionRecord, point1_y_milli));
geometry.response_record = delta == 1 ? 68 : 66;
} else if (flipper == 2) {
geometry.radius = borland_real48_round_to_i32(
record_real48(80, RECORD_RADIUS));
geometry.pivot_x = add_wrap_i32(
record_i32(80, offsetof(TdkpinCollisionRecord, point1_x_milli)),
10000);
geometry.pivot_y = record_i32(
80, offsetof(TdkpinCollisionRecord, point1_y_milli));
geometry.unused_negative_point_x = record_i32(
83, offsetof(TdkpinCollisionRecord, point1_x_milli));
geometry.negative_edge_x = subtract_wrap_i32(
record_i32(83, offsetof(TdkpinCollisionRecord, point1_x_milli)),
20000);
geometry.negative_edge_y = record_i32(
83, offsetof(TdkpinCollisionRecord, point1_y_milli));
geometry.unused_positive_point_x = record_i32(
81, offsetof(TdkpinCollisionRecord, point1_x_milli));
geometry.positive_edge_x = subtract_wrap_i32(
record_i32(81, offsetof(TdkpinCollisionRecord, point1_x_milli)),
20000);
geometry.positive_edge_y = record_i32(
81, offsetof(TdkpinCollisionRecord, point1_y_milli));
geometry.response_record = delta == 1 ? 81 : 83;
} else {
geometry = (FlipperCollisionGeometry){
.radius = indeterminate_i32(),
.pivot_x = indeterminate_i32(),
.pivot_y = indeterminate_i32(),
.unused_negative_point_x = indeterminate_i32(),
.unused_positive_point_x = indeterminate_i32(),
.negative_edge_x = indeterminate_i32(),
.negative_edge_y = indeterminate_i32(),
.positive_edge_x = indeterminate_i32(),
.positive_edge_y = indeterminate_i32(),
.response_record = win16_indeterminate_u16(),
};
}
geometry.pivot_y = add_wrap_i32(
geometry.pivot_y,
borland_multiply_i32(delta, geometry.radius));
(void)geometry.unused_negative_point_x;
(void)geometry.unused_positive_point_x;
return geometry;
}
static int32_t cross_product_for_edge(
int32_t edge_x,
int32_t edge_y,
int32_t pivot_x,
int32_t pivot_y,
int32_t ball_x,
int32_t ball_y)
{
int32_t edge_from_ball_x = divide_by_thousand(
subtract_wrap_i32(edge_x, ball_x));
int32_t edge_from_pivot_y = divide_by_thousand(
subtract_wrap_i32(edge_y, pivot_y));
int32_t edge_from_ball_y = divide_by_thousand(
subtract_wrap_i32(edge_y, ball_y));
int32_t edge_from_pivot_x = divide_by_thousand(
subtract_wrap_i32(edge_x, pivot_x));
return subtract_wrap_i32(
borland_multiply_i32(edge_from_ball_y, edge_from_pivot_x),
borland_multiply_i32(edge_from_ball_x, edge_from_pivot_y));
}
static int32_t radial_distance_milli(
int32_t ball_x, int32_t ball_y, int32_t pivot_x, int32_t pivot_y)
{
BorlandReal48 dx = borland_real48_divide(
borland_i32_to_real48(subtract_wrap_i32(ball_x, pivot_x)),
g_real48_thousand);
BorlandReal48 dy = borland_real48_divide(
borland_i32_to_real48(subtract_wrap_i32(ball_y, pivot_y)),
g_real48_thousand);
BorlandReal48 squared = borland_real48_add(
borland_real48_square(dx), borland_real48_square(dy));
return borland_real48_round_to_i32(
borland_real48_multiply(
borland_real48_sqrt(squared), g_real48_thousand));
}
static bool point_in_moving_flipper(
const FlipperCollisionGeometry *geometry,
int32_t delta,
uint16_t flipper,
uint16_t mode,
int32_t ball_x,
int32_t ball_y,
int32_t *distance)
{
int32_t first_cross;
int32_t second_cross;
if (delta == -1) {
first_cross = cross_product_for_edge(
geometry->negative_edge_x,
subtract_wrap_i32(geometry->negative_edge_y, 43000),
geometry->pivot_x,
geometry->pivot_y,
ball_x,
ball_y);
second_cross = cross_product_for_edge(
geometry->negative_edge_x,
add_wrap_i32(geometry->negative_edge_y, 12000),
geometry->pivot_x,
geometry->pivot_y,
ball_x,
ball_y);
} else {
first_cross = cross_product_for_edge(
geometry->positive_edge_x,
subtract_wrap_i32(geometry->positive_edge_y, 12000),
geometry->pivot_x,
geometry->pivot_y,
ball_x,
ball_y);
second_cross = cross_product_for_edge(
geometry->positive_edge_x,
add_wrap_i32(geometry->positive_edge_y, 43000),
geometry->pivot_x,
geometry->pivot_y,
ball_x,
ball_y);
}
int32_t absolute_x = absolute_wrap_i32(
subtract_wrap_i32(ball_x, geometry->pivot_x));
int32_t absolute_y = absolute_wrap_i32(
subtract_wrap_i32(ball_y, geometry->pivot_y));
if (absolute_x >= COLLISION_SEARCH_RADIUS ||
absolute_y >= COLLISION_SEARCH_RADIUS) {
return false;
}
*distance = radial_distance_milli(
ball_x, ball_y, geometry->pivot_x, geometry->pivot_y);
if (*distance > COLLISION_SEARCH_RADIUS || mode != 0) {
return false;
}
if (flipper == 1) {
return first_cross > -1000 && second_cross < 1000 &&
ball_x > geometry->pivot_x;
}
if (flipper == 2) {
return first_cross < 1000 && second_cross > -1000 &&
ball_x < geometry->pivot_x;
}
return false;
}
static int32_t collision_penetration(
const FlipperCollisionGeometry *geometry,
int32_t delta,
int32_t ball_x,
int32_t ball_y)
{
int32_t collision_y;
if (delta == -1) {
int32_t edge_dx = absolute_wrap_i32(subtract_wrap_i32(
geometry->negative_edge_x, geometry->pivot_x));
if (borland_real48_compare(
borland_i32_to_real48(edge_dx), g_real48_one_tenth) > 0) {
collision_y = add_wrap_i32(
subtract_wrap_i32(
geometry->pivot_y,
absolute_wrap_i32(subtract_wrap_i32(
ball_x, geometry->pivot_x))),
5000);
} else {
collision_y = subtract_wrap_i32(
geometry->negative_edge_y, 43000);
}
} else {
int32_t edge_dx = subtract_wrap_i32(
geometry->positive_edge_x, geometry->pivot_x);
if (borland_real48_compare(
borland_i32_to_real48(absolute_wrap_i32(edge_dx)),
g_real48_one_tenth) > 0) {
int32_t numerator = borland_multiply_i32(
subtract_wrap_i32(ball_x, geometry->pivot_x),
subtract_wrap_i32(
add_wrap_i32(geometry->positive_edge_y, 43000),
geometry->pivot_y));
int32_t interpolation = borland_real48_round_to_i32(
borland_real48_divide(
borland_i32_to_real48(numerator),
borland_i32_to_real48(edge_dx)));
collision_y = subtract_wrap_i32(
geometry->pivot_y, interpolation);
} else {
collision_y = add_wrap_i32(
geometry->positive_edge_y, 43000);
}
}
int32_t penetration = subtract_wrap_i32(collision_y, ball_y);
if (penetration < 0) {
return negate_wrap_i32(
borland_multiply_i32(penetration, delta));
}
return 0;
}
static void apply_flipper_response(
Win16FarPtr receiver,
const FlipperCollisionGeometry *geometry,
int32_t delta,
uint16_t flipper,
int32_t ball_x,
int32_t ball_y,
int32_t distance,
int32_t penetration,
uint16_t caller_bp)
{
int32_t normal_x_integer = borland_multiply_i32(
subtract_wrap_i32(ball_x, geometry->pivot_x), delta);
int32_t normal_y_integer = borland_multiply_i32(
add_wrap_i32(
subtract_wrap_i32(ball_y, geometry->pivot_y), 4000),
delta);
BorlandReal48 normal_x = borland_i32_to_real48(normal_x_integer);
BorlandReal48 normal_y = borland_i32_to_real48(normal_y_integer);
if (flipper == 2) {
normal_x = negate_real48(normal_x);
normal_y = negate_real48(normal_y);
}
int32_t velocity_x = (int32_t)win16_read_u32(
win16_far_add_offset(receiver, 0x0baa));
int32_t velocity_y = (int32_t)win16_read_u32(
win16_far_add_offset(receiver, 0x0bae));
BorlandReal48 response_radius =
borland_i32_to_real48(COLLISION_RESPONSE_RADIUS);
/* 1000:94ce-9553 computes this projection and then deliberately clears it. */
BorlandReal48 discarded_projection_real = borland_real48_divide(
borland_real48_multiply(
borland_real48_add(
borland_real48_multiply(
borland_i32_to_real48(velocity_y), normal_y),
borland_real48_multiply(
borland_i32_to_real48(velocity_x), normal_x)),
g_real48_one_half),
response_radius);
(void)borland_real48_round_to_i32(discarded_projection_real);
int32_t normal_projection = 0;
int32_t tangent_projection = borland_real48_round_to_i32(
borland_real48_divide(
borland_real48_subtract(
borland_real48_multiply(
borland_i32_to_real48(velocity_x), normal_y),
borland_real48_multiply(
borland_i32_to_real48(velocity_y), normal_x)),
response_radius));
BorlandReal48 gain = borland_real48_add(
borland_real48_multiply(
borland_real48_sqrt(borland_real48_divide(
borland_i32_to_real48(distance), response_radius)),
g_real48_two),
g_real48_two_fifths);
BorlandReal48 record_response = record_real48(
geometry->response_record, RECORD_RESPONSE_NORMAL);
tangent_projection = add_wrap_i32(
borland_real48_round_to_i32(
borland_real48_multiply(
borland_i32_to_real48(tangent_projection),
borland_real48_add(g_real48_one, record_response))),
borland_real48_round_to_i32(
borland_real48_multiply(
borland_i32_to_real48((int32_t)win16_read_u32(
win16_far_add_offset(receiver, 0x56))),
gain)));
int32_t delta_velocity_x = borland_real48_round_to_i32(
borland_real48_divide(
borland_real48_subtract(
borland_real48_multiply(
borland_i32_to_real48(normal_projection), normal_x),
borland_real48_multiply(
borland_i32_to_real48(tangent_projection), normal_y)),
response_radius));
velocity_x = add_wrap_i32(velocity_x, delta_velocity_x);
win16_write_u32(receiver, 0x0baa, (uint32_t)velocity_x);
int32_t delta_velocity_y = borland_real48_round_to_i32(
borland_real48_divide(
borland_real48_add(
borland_real48_multiply(
borland_i32_to_real48(tangent_projection), normal_x),
borland_real48_multiply(
borland_i32_to_real48(normal_projection), normal_y)),
response_radius));
velocity_y = add_wrap_i32(velocity_y, delta_velocity_y);
win16_write_u32(receiver, 0x0bae, (uint32_t)velocity_y);
int32_t movement_x = borland_real48_round_to_i32(
borland_real48_divide(
borland_i32_to_real48(
borland_multiply_i32(penetration, velocity_x)),
borland_i32_to_real48(velocity_y)));
tdkpin_move_ball_and_render(
caller_bp, movement_x, penetration);
}
void tdkpin_move_flipper_collision_geometry(
uint16_t caller_bp,
int32_t delta,
uint16_t flipper,
uint16_t mode)
{
tdkpin_play_sound_if_not_tilted(21);
Win16FarPtr receiver = caller_receiver(caller_bp);
FlipperCollisionGeometry geometry =
load_flipper_geometry(delta, flipper);
uint16_t ball_count = win16_read_u8(
win16_far_add_offset(receiver, 0x61));
for (uint16_t ball = 1; ball <= ball_count; ball++) {
if (win16_read_u8(win16_far_add_offset(receiver, 0x61)) > 1) {
tdkpin_load_ball_slot(caller_bp, (uint16_t)(ball - 1u));
}
int32_t ball_x = (int32_t)win16_read_u32(
win16_dgroup_pointer(0x07d3));
int32_t ball_y = (int32_t)win16_read_u32(
win16_dgroup_pointer(0x07d7));
int32_t distance = 0;
if (point_in_moving_flipper(
&geometry,
delta,
flipper,
mode,
ball_x,
ball_y,
&distance)) {
int32_t penetration = collision_penetration(
&geometry, delta, ball_x, ball_y);
apply_flipper_response(
receiver,
&geometry,
delta,
flipper,
ball_x,
ball_y,
distance,
penetration,
caller_bp);
}
if (win16_read_u8(win16_far_add_offset(receiver, 0x61)) > 1) {
tdkpin_save_ball_slot(caller_bp, (uint16_t)(ball - 1u));
}
}
}