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:
2026-08-23 16:41:17 +02:00
parent aef404c834
commit 8b99e9607c
253 changed files with 79031 additions and 51 deletions
@@ -0,0 +1,319 @@
#include "../tdkpin_collision_events.h"
#include <assert.h>
#include <string.h>
uint8_t g_current_player;
static uint8_t g_stack[0x400];
static uint8_t g_object[0x0c00];
static uint8_t g_dgroup[0x5000];
static Win16FarPtr g_receiver;
static uint16_t g_restore_ids[64];
static uint16_t g_refresh_ids[64];
static uint16_t g_sounds[32];
static int32_t g_scores[32];
static unsigned g_restore_calls;
static unsigned g_refresh_calls;
static unsigned g_sound_calls;
static unsigned g_score_calls;
static unsigned g_draw_score_calls;
static unsigned g_composite_calls;
static unsigned g_present_calls;
static unsigned g_restore_background_calls;
static unsigned g_get_dc_calls;
static unsigned g_release_dc_calls;
static unsigned g_random_calls;
static uint16_t g_random_values[4];
static uint16_t g_last_sprite;
static int32_t g_last_drawn_score;
static Win16FarPtr record(uint16_t id)
{
return win16_dgroup_pointer((uint16_t)(0x095b + id * 0x53u));
}
void tdkpin_restore_record_or_item_a(uint16_t caller_bp, uint16_t id)
{
assert(caller_bp == 0x0100 && g_restore_calls < 64);
g_restore_ids[g_restore_calls++] = id;
}
void tdkpin_refresh_collision_record(uint16_t caller_bp, uint16_t id)
{
assert(caller_bp == 0x0100 && g_refresh_calls < 64);
g_refresh_ids[g_refresh_calls++] = id;
}
void tdkpin_play_sound_if_not_tilted(uint16_t sound_number)
{
assert(g_sound_calls < 32);
g_sounds[g_sound_calls++] = sound_number;
}
void tdkpin_add_score_from_caller_frame(uint16_t caller_bp, int32_t delta)
{
assert(caller_bp == 0x0100 && g_score_calls < 32);
g_scores[g_score_calls++] = delta;
}
void tdkpin_draw_score_from_caller_frame(
uint16_t caller_bp, uint16_t low, uint16_t high)
{
assert(caller_bp == 0x0100);
g_last_drawn_score =
(int32_t)((uint32_t)low | ((uint32_t)high << 16));
g_draw_score_calls++;
}
uint16_t borland_random_below(uint16_t upper_bound)
{
assert(upper_bound == 6 && g_random_calls < 4);
return g_random_values[g_random_calls++];
}
int32_t borland_multiply_i32(int32_t left, int32_t right)
{
return (int32_t)((uint32_t)left * (uint32_t)right);
}
HDC16 GetDC16(HWND16 window)
{
assert(window == 0x2222);
g_get_dc_calls++;
return 0x3333;
}
INT16 ReleaseDC16(HWND16 window, HDC16 dc)
{
assert(window == 0x2222 && dc == 0x3333);
g_release_dc_calls++;
return 1;
}
void tdkpin_composite_player_sprite(uint16_t sprite_index, HDC16 dc)
{
assert(dc == 0x3333);
g_last_sprite = sprite_index;
g_composite_calls++;
}
void tdkpin_restore_background_region_a(
INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc)
{
assert(height > 0 && width > 0 && dc == 0x3333);
(void)y;
(void)x;
g_restore_background_calls++;
}
void tdkpin_present_background_region(
INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc)
{
assert(height > 0 && width > 0 && dc == 0x3333);
(void)y;
(void)x;
g_present_calls++;
}
static void reset_counters(void)
{
memset(g_restore_ids, 0, sizeof(g_restore_ids));
memset(g_refresh_ids, 0, sizeof(g_refresh_ids));
memset(g_sounds, 0, sizeof(g_sounds));
memset(g_scores, 0, sizeof(g_scores));
g_restore_calls = 0;
g_refresh_calls = 0;
g_sound_calls = 0;
g_score_calls = 0;
g_draw_score_calls = 0;
g_composite_calls = 0;
g_present_calls = 0;
g_restore_background_calls = 0;
g_get_dc_calls = 0;
g_release_dc_calls = 0;
g_random_calls = 0;
memset(g_random_values, 0, sizeof(g_random_values));
g_last_sprite = 0;
g_last_drawn_score = 0;
}
static void prepare_fixture(void)
{
memset(g_stack, 0, sizeof(g_stack));
memset(g_object, 0, sizeof(g_object));
memset(g_dgroup, 0, sizeof(g_dgroup));
win16_reset_segment_bindings();
win16_bind_segment(0x7000, g_stack, sizeof(g_stack), true);
win16_bind_segment(0x7100, g_object, sizeof(g_object), true);
win16_bind_segment(0x7200, g_dgroup, sizeof(g_dgroup), true);
win16_set_dgroup_selector(0x7200);
win16_set_stack_state(0x7000, 0);
g_receiver = win16_make_far_pointer(0x7100, 0);
win16_write_stack_u16(0x0106, win16_far_offset(g_receiver));
win16_write_stack_u16(0x0108, win16_far_selector(g_receiver));
win16_write_u16(g_receiver, 4, 0x2222);
g_current_player = 2;
reset_counters();
}
static void seed_bounds(uint16_t id, INT16 left, INT16 top)
{
win16_write_u16(record(id), 0x4b, (uint16_t)left);
win16_write_u16(record(id), 0x4d, (uint16_t)top);
win16_write_u16(record(id), 0x4f, (uint16_t)(left + 2));
win16_write_u16(record(id), 0x51, (uint16_t)(top + 3));
}
static void test_tilt_and_combined_low_events(void)
{
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x07c6), 1);
tdkpin_handle_collision_events(0x0100, 0xffff, 129);
assert(g_restore_calls == 0 && g_sound_calls == 0);
prepare_fixture();
win16_write_u16(record(129), 0x43, 20);
win16_write_u8(win16_far_add_offset(record(20), 0x34), 1);
win16_write_u8(win16_far_add_offset(record(21), 0x34), 1);
win16_write_u8(win16_far_add_offset(record(22), 0x34), 1);
tdkpin_handle_collision_events(
0x0100, 0x0001u | 0x0002u | 0x2000u | 0x0400u, 129);
assert(win16_read_u8(win16_far_add_offset(
g_receiver, (uint16_t)(0x0467 + 2u * 20u + 15u))) == 1);
assert(g_restore_calls == 1 && g_restore_ids[0] == 1015);
assert(g_refresh_calls == 1 && g_refresh_ids[0] == 20);
assert(win16_read_u8(win16_far_add_offset(record(20), 0x34)) == 0);
assert(win16_read_u8(win16_far_add_offset(record(21), 0x34)) == 0);
assert(win16_read_u8(win16_far_add_offset(record(22), 0x34)) == 0);
assert(win16_read_u32(win16_dgroup_pointer(0x0855)) == 0);
assert(win16_read_u8(win16_dgroup_pointer(0x07c7)) == 1);
}
static void test_sprite_award(void)
{
prepare_fixture();
win16_write_u16(win16_dgroup_pointer(0x09a8), 0, 2);
g_random_values[0] = 1;
g_random_values[1] = 2;
tdkpin_handle_collision_events(0x0100, 0x0004, 0);
assert(g_sound_calls == 1 && g_sounds[0] == 12);
assert(win16_read_u8(win16_far_add_offset(record(149), 0x34)) == 1);
assert(g_restore_ids[0] == 149 && g_random_calls == 2);
assert(win16_read_u16(win16_dgroup_pointer(0x09a8)) == 3);
assert(g_composite_calls == 1 && g_last_sprite == 3);
assert(g_present_calls == 1 && g_get_dc_calls == 1);
prepare_fixture();
win16_write_u16(win16_dgroup_pointer(0x399a), 0, 1);
tdkpin_handle_collision_events(0x0100, 0x0004, 0);
assert(win16_read_u16(win16_dgroup_pointer(0x09a8)) == 7);
assert(g_random_calls == 0 && g_last_sprite == 7);
}
static void set_group_heads(uint16_t first, uint16_t groups, uint8_t active)
{
for (uint16_t group = 0; group < groups; group++) {
win16_write_u8(
win16_far_add_offset(record((uint16_t)(first + group * 3u)), 0x34),
active);
}
}
static void test_ready_bank(void)
{
prepare_fixture();
set_group_heads(90, 5, 0);
win16_write_u8(win16_dgroup_pointer(0x0818), 6);
tdkpin_handle_collision_events(0x0100, 0x0008, 0);
assert(g_sound_calls == 2 && g_sounds[0] == 12 && g_sounds[1] == 17);
assert(g_score_calls == 1 && g_scores[0] == 50000);
assert(g_refresh_calls == 5);
prepare_fixture();
set_group_heads(90, 5, 0);
win16_write_u8(win16_dgroup_pointer(0x0818), 2);
tdkpin_handle_collision_events(0x0100, 0x0008, 0);
assert(win16_read_u8(win16_dgroup_pointer(0x0818)) == 3);
assert(g_restore_calls == 1 && g_restore_ids[0] == 1002);
assert(g_score_calls == 0 && g_refresh_calls == 5);
}
static void set_item_bank_count(uint16_t count)
{
for (uint16_t item = 1; item <= count; item++) {
win16_write_u8(win16_far_add_offset(
g_receiver, (uint16_t)(0x046c + 2u * 20u + item)), 1);
}
}
static void test_item_bank_branches(void)
{
prepare_fixture();
set_group_heads(109, 4, 0);
tdkpin_handle_collision_events(0x0100, 0x0010, 0);
assert(g_restore_ids[0] == 1006 && g_scores[0] == 10000);
assert(g_refresh_calls == 4);
prepare_fixture();
set_group_heads(109, 4, 0);
set_item_bank_count(8);
seed_bounds(153, 1, 2);
seed_bounds(6, 3, 4);
seed_bounds(154, 5, 6);
tdkpin_handle_collision_events(0x0100, 0x0010, 0);
assert(g_restore_ids[0] == 1014 && g_scores[0] == 24464);
assert(win16_read_u8(win16_far_add_offset(g_receiver, 0x0bdc)) == 1);
assert(g_restore_background_calls == 3 && g_present_calls == 3);
assert(g_get_dc_calls == 1 && g_release_dc_calls == 1);
prepare_fixture();
set_group_heads(109, 4, 0);
set_item_bank_count(9);
win16_write_u8(win16_far_add_offset(g_receiver, 0x0bdc), 1);
win16_write_u32(win16_dgroup_pointer(0x0996), 0, 10);
win16_write_u8(win16_dgroup_pointer(0x09a3), 3);
tdkpin_handle_collision_events(0x0100, 0x0010, 0);
assert(win16_read_u32(win16_dgroup_pointer(0x0996)) == 100010u);
assert(g_draw_score_calls == 1 && g_last_drawn_score == 300030);
assert(g_sounds[1] == 7);
}
static void test_switch_and_timed_records(void)
{
static const uint16_t event_ids[3] = {150, 151, 152};
static const uint16_t related[3] = {153, 6, 154};
for (uint16_t index = 0; index < 3; index++) {
prepare_fixture();
tdkpin_handle_collision_events(0x0100, 0x1000, event_ids[index]);
assert(win16_read_u8(win16_far_add_offset(
g_receiver, (uint16_t)(0x00f7 + index))) == 10);
assert(g_restore_calls == 2);
assert(g_restore_ids[0] == event_ids[index]);
assert(g_restore_ids[1] == related[index]);
assert(win16_read_u8(win16_far_add_offset(
record(related[index]), 0x34)) == 1);
}
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x0818), 3);
tdkpin_handle_collision_events(0x0100, 0x0040, 51);
assert(g_sounds[0] == 6 && g_restore_ids[0] == 51);
assert(g_scores[0] == 2000);
assert(win16_read_u8(win16_far_add_offset(g_receiver, 0x94)) == 5);
prepare_fixture();
tdkpin_handle_collision_events(0x0100, 0x0040, 140);
assert(win16_read_u8(win16_far_add_offset(g_receiver, 0x00ed)) == 20);
assert(g_restore_ids[0] == 140 && g_sound_calls == 0);
}
int main(void)
{
test_tilt_and_combined_low_events();
test_sprite_award();
test_ready_bank();
test_item_bank_branches();
test_switch_and_timed_records();
return 0;
}