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
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
#include "../tdkpin_flippers.h"
|
||||
|
||||
#include "../tdkpin_borland_runtime.h"
|
||||
#include "../tdkpin_collision_records.h"
|
||||
#include "../tdkpin_real48.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint8_t g_dgroup[0x5000];
|
||||
static uint8_t g_objects[0x1000];
|
||||
static uint8_t g_stack[0x1000];
|
||||
static Win16FarPtr g_receiver;
|
||||
static uint16_t g_sounds[4];
|
||||
static unsigned g_sound_count;
|
||||
static unsigned g_move_calls;
|
||||
static int32_t g_move_x;
|
||||
static int32_t g_move_y;
|
||||
static unsigned g_load_calls;
|
||||
static unsigned g_save_calls;
|
||||
|
||||
uint8_t g_borland_cpu_level = 2;
|
||||
|
||||
_Noreturn void borland_runtime_error(uint16_t code)
|
||||
{
|
||||
(void)code;
|
||||
abort();
|
||||
}
|
||||
|
||||
_Noreturn void win16_integer_divide_fault(void)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
void tdkpin_play_sound_if_not_tilted(uint16_t sound_number)
|
||||
{
|
||||
assert(g_sound_count < sizeof(g_sounds) / sizeof(g_sounds[0]));
|
||||
g_sounds[g_sound_count++] = sound_number;
|
||||
}
|
||||
|
||||
void tdkpin_move_ball_and_render(
|
||||
uint16_t caller_bp, int32_t delta_x, int32_t delta_y)
|
||||
{
|
||||
assert(caller_bp == 0x0300);
|
||||
g_move_calls++;
|
||||
g_move_x = delta_x;
|
||||
g_move_y = delta_y;
|
||||
}
|
||||
|
||||
void tdkpin_load_ball_slot(uint16_t caller_bp, uint16_t slot)
|
||||
{
|
||||
assert(caller_bp == 0x0300);
|
||||
assert(slot == g_load_calls);
|
||||
g_load_calls++;
|
||||
}
|
||||
|
||||
void tdkpin_save_ball_slot(uint16_t caller_bp, uint16_t slot)
|
||||
{
|
||||
assert(caller_bp == 0x0300);
|
||||
assert(slot == g_save_calls);
|
||||
g_save_calls++;
|
||||
}
|
||||
|
||||
static Win16FarPtr record(uint16_t id)
|
||||
{
|
||||
return win16_dgroup_pointer((uint16_t)(
|
||||
0x095b + id * sizeof(TdkpinCollisionRecord)));
|
||||
}
|
||||
|
||||
static void write_i32(uint16_t id, size_t offset, int32_t value)
|
||||
{
|
||||
win16_write_u32(record(id), (uint16_t)offset, (uint32_t)value);
|
||||
}
|
||||
|
||||
static void write_real48(
|
||||
uint16_t id, size_t offset, BorlandReal48 value)
|
||||
{
|
||||
Win16FarPtr destination = win16_far_add_offset(
|
||||
record(id), (uint16_t)offset);
|
||||
for (uint16_t index = 0; index < sizeof(value.bytes); index++) {
|
||||
win16_write_u8(
|
||||
win16_far_add_offset(destination, index), value.bytes[index]);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_points(
|
||||
uint16_t id,
|
||||
int32_t point1_x,
|
||||
int32_t point1_y,
|
||||
int32_t point2_x,
|
||||
int32_t point2_y)
|
||||
{
|
||||
write_i32(id, offsetof(TdkpinCollisionRecord, point1_x_milli), point1_x);
|
||||
write_i32(id, offsetof(TdkpinCollisionRecord, point1_y_milli), point1_y);
|
||||
write_i32(id, offsetof(TdkpinCollisionRecord, point2_x_milli), point2_x);
|
||||
write_i32(id, offsetof(TdkpinCollisionRecord, point2_y_milli), point2_y);
|
||||
}
|
||||
|
||||
static void prepare_fixture(void)
|
||||
{
|
||||
static const BorlandReal48 one_half =
|
||||
{{0x80, 0, 0, 0, 0, 0}};
|
||||
static const BorlandReal48 one_quarter =
|
||||
{{0x7f, 0, 0, 0, 0, 0}};
|
||||
static const BorlandReal48 three_quarters =
|
||||
{{0x80, 0, 0, 0, 0, 0x40}};
|
||||
static const BorlandReal48 zero =
|
||||
{{0, 0, 0, 0, 0, 0}};
|
||||
memset(g_dgroup, 0, sizeof(g_dgroup));
|
||||
memset(g_objects, 0, sizeof(g_objects));
|
||||
memset(g_stack, 0, sizeof(g_stack));
|
||||
win16_reset_segment_bindings();
|
||||
win16_bind_segment(0x7000, g_objects, sizeof(g_objects), true);
|
||||
win16_bind_segment(0x7200, g_dgroup, sizeof(g_dgroup), true);
|
||||
win16_bind_segment(0x7300, g_stack, sizeof(g_stack), true);
|
||||
win16_set_dgroup_selector(0x7200);
|
||||
win16_set_stack_state(0x7300, 0);
|
||||
g_receiver = win16_make_far_pointer(0x7000, 0);
|
||||
win16_write_stack_u16(0x0306, win16_far_offset(g_receiver));
|
||||
win16_write_stack_u16(0x0308, win16_far_selector(g_receiver));
|
||||
win16_write_u8(win16_far_add_offset(g_receiver, 0x61), 1);
|
||||
win16_write_u32(g_receiver, 0x56, 1000);
|
||||
|
||||
write_points(65, 103000, 397000, 0, 0);
|
||||
write_real48(
|
||||
65,
|
||||
offsetof(TdkpinCollisionRecord, radius_milli),
|
||||
borland_i32_to_real48(15000));
|
||||
write_points(66, 112000, 386000, 141000, 413000);
|
||||
write_real48(
|
||||
66, offsetof(TdkpinCollisionRecord, response_normal), one_half);
|
||||
write_points(68, 130000, 427000, 97000, 411000);
|
||||
write_real48(
|
||||
68, offsetof(TdkpinCollisionRecord, response_normal), one_quarter);
|
||||
|
||||
write_points(80, 210000, 397000, 0, 0);
|
||||
write_real48(
|
||||
80,
|
||||
offsetof(TdkpinCollisionRecord, radius_milli),
|
||||
borland_i32_to_real48(15000));
|
||||
write_points(81, 216000, 411000, 177000, 427000);
|
||||
write_real48(
|
||||
81, offsetof(TdkpinCollisionRecord, response_normal), three_quarters);
|
||||
write_points(83, 172000, 413000, 201000, 386000);
|
||||
write_real48(
|
||||
83, offsetof(TdkpinCollisionRecord, response_normal), zero);
|
||||
|
||||
g_sound_count = 0;
|
||||
g_move_calls = 0;
|
||||
g_move_x = 0;
|
||||
g_move_y = 0;
|
||||
g_load_calls = 0;
|
||||
g_save_calls = 0;
|
||||
}
|
||||
|
||||
static void set_ball(int32_t x, int32_t y, int32_t vx, int32_t vy)
|
||||
{
|
||||
win16_write_u32(win16_dgroup_pointer(0x07d3), 0, (uint32_t)x);
|
||||
win16_write_u32(win16_dgroup_pointer(0x07d7), 0, (uint32_t)y);
|
||||
win16_write_u32(g_receiver, 0x0baa, (uint32_t)vx);
|
||||
win16_write_u32(g_receiver, 0x0bae, (uint32_t)vy);
|
||||
}
|
||||
|
||||
static void test_left_negative_contact(void)
|
||||
{
|
||||
prepare_fixture();
|
||||
set_ball(104000, 384000, 1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_receiver, 0x0baa)) == 1266);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_receiver, 0x0bae)) == 1511);
|
||||
assert(g_move_x == -6703 && g_move_y == -8000);
|
||||
assert(g_sound_count == 1 && g_sounds[0] == 21);
|
||||
assert(g_move_calls == 1);
|
||||
}
|
||||
|
||||
static void test_right_positive_contact(void)
|
||||
{
|
||||
prepare_fixture();
|
||||
set_ball(209000, 419000, -1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, 1, 2, 0);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_receiver, 0x0baa)) == -737);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_receiver, 0x0bae)) == 2263);
|
||||
assert(g_move_x == -8549 && g_move_y == 26250);
|
||||
assert(g_sound_count == 1 && g_sounds[0] == 21);
|
||||
assert(g_move_calls == 1);
|
||||
}
|
||||
|
||||
static void test_left_positive_contact(void)
|
||||
{
|
||||
prepare_fixture();
|
||||
set_ball(104000, 421000, 1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, 1, 1, 0);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_receiver, 0x0baa)) == 622);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_receiver, 0x0bae)) == 2320);
|
||||
assert(g_move_x == 5414 && g_move_y == 20193);
|
||||
assert(g_move_calls == 1);
|
||||
}
|
||||
|
||||
static void test_right_negative_contact(void)
|
||||
{
|
||||
prepare_fixture();
|
||||
set_ball(209000, 385000, -1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, -1, 2, 0);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_receiver, 0x0baa)) == -1280);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_receiver, 0x0bae)) == 1560);
|
||||
assert(g_move_x == 7385 && g_move_y == -9000);
|
||||
assert(g_move_calls == 1);
|
||||
}
|
||||
|
||||
static void test_vertical_edge_fallbacks(void)
|
||||
{
|
||||
prepare_fixture();
|
||||
write_i32(
|
||||
66,
|
||||
offsetof(TdkpinCollisionRecord, point2_x_milli),
|
||||
73000);
|
||||
set_ball(94000, 371000, 1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
|
||||
assert(g_move_calls == 1 && g_move_y == -1000);
|
||||
|
||||
prepare_fixture();
|
||||
write_i32(
|
||||
81,
|
||||
offsetof(TdkpinCollisionRecord, point1_x_milli),
|
||||
240000);
|
||||
set_ball(197000, 455000, -1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, 1, 2, 0);
|
||||
assert(g_move_calls == 1 && g_move_y == 1000);
|
||||
}
|
||||
|
||||
static void test_multiball_slot_loop_and_invalid_flipper(void)
|
||||
{
|
||||
prepare_fixture();
|
||||
win16_write_u8(win16_far_add_offset(g_receiver, 0x61), 2);
|
||||
set_ball(300000, 300000, 1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
|
||||
assert(g_load_calls == 2 && g_save_calls == 2);
|
||||
assert(g_move_calls == 0);
|
||||
|
||||
prepare_fixture();
|
||||
win16_write_u8(win16_far_add_offset(g_receiver, 0x61), 0);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, 7, 3, 0);
|
||||
assert(g_sound_count == 1 && g_sounds[0] == 21);
|
||||
assert(g_move_calls == 0 && g_load_calls == 0 && g_save_calls == 0);
|
||||
}
|
||||
|
||||
static void test_gates(void)
|
||||
{
|
||||
prepare_fixture();
|
||||
set_ball(300000, 300000, 1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
|
||||
assert(g_move_calls == 0);
|
||||
|
||||
prepare_fixture();
|
||||
set_ball(104000, 384000, 1000, 2000);
|
||||
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 1);
|
||||
assert(g_move_calls == 0);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_left_negative_contact();
|
||||
test_right_positive_contact();
|
||||
test_left_positive_contact();
|
||||
test_right_negative_contact();
|
||||
test_vertical_edge_fallbacks();
|
||||
test_multiball_slot_loop_and_invalid_flipper();
|
||||
test_gates();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user