Files
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

364 lines
11 KiB
C

#include "../tdkpin_turn_state.h"
#include <assert.h>
#include <string.h>
uint8_t g_player_count;
uint8_t g_current_player;
uint8_t g_gameplay_transition_pending;
int32_t g_ball_x_milli;
int32_t g_ball_y_milli;
static uint8_t g_stack[0x400];
static uint8_t g_object[0x0c00];
static uint8_t g_dgroup[0x5000];
static Win16FarPtr g_receiver;
static unsigned g_restore_previous_calls;
static unsigned g_restore_record_b_calls;
static unsigned g_dynamic_record_calls;
static unsigned g_sound_calls;
static unsigned g_load_highscores_calls;
static unsigned g_insert_score_calls;
static unsigned g_transition_calls;
static unsigned g_show_highscore_calls;
static unsigned g_collision_refresh_calls;
static unsigned g_item_refresh_calls;
static unsigned g_restore_item_calls;
static unsigned g_draw_item_calls;
static unsigned g_status_calls;
static unsigned g_redraw_calls;
static unsigned g_reset_ball_calls;
static unsigned g_restore_b_calls;
static unsigned g_present_calls;
static unsigned g_marker_refresh_calls;
static unsigned g_get_dc_calls;
static unsigned g_release_dc_calls;
static uint16_t g_last_item_count;
static uint8_t g_last_status_player;
void tdkpin_restore_previous_ball_region(
uint16_t caller_bp, uint8_t suppress_present)
{
assert(caller_bp == 0x0100 && suppress_present == 0);
g_restore_previous_calls++;
}
void tdkpin_restore_record_b(uint16_t caller_bp, uint16_t id)
{
assert(caller_bp == 0x0100 && id == 1020);
g_restore_record_b_calls++;
}
void tdkpin_update_dynamic_ball_record(
uint16_t ignored_static_link, int32_t displacement)
{
assert(ignored_static_link == 0x0100 && displacement == 1);
g_dynamic_record_calls++;
}
BorlandReal48 borland_i32_to_real48(int32_t value)
{
assert(value == 1000);
return (BorlandReal48){{0x81, 1, 2, 3, 4, 5}};
}
BorlandReal48 borland_real48_multiply(
BorlandReal48 left, BorlandReal48 right)
{
static const uint8_t expected_left[6] = {0x81, 1, 2, 3, 4, 5};
static const uint8_t expected_right[6] = {
0x80, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c};
assert(memcmp(left.bytes, expected_left, 6) == 0);
assert(memcmp(right.bytes, expected_right, 6) == 0);
return (BorlandReal48){{0x82, 6, 7, 8, 9, 10}};
}
int32_t borland_real48_round_to_i32(BorlandReal48 value)
{
static const uint8_t expected[6] = {0x82, 6, 7, 8, 9, 10};
assert(memcmp(value.bytes, expected, 6) == 0);
return 800;
}
void tdkpin_play_sound_if_not_tilted(uint16_t sound_number)
{
assert(sound_number == 8);
g_sound_calls++;
}
void tdkpin_load_highscores(uint16_t ignored_static_link)
{
assert(ignored_static_link == 0x0100);
g_load_highscores_calls++;
}
void tdkpin_insert_current_score(uint16_t caller_bp)
{
assert(caller_bp == 0x0100);
g_insert_score_calls++;
}
void tdkpin_set_gameplay_transition_flags(uint16_t caller_bp)
{
assert(caller_bp == 0x0100);
g_transition_calls++;
}
void tdkpin_show_highscore_windows(uint16_t caller_bp)
{
assert(caller_bp == 0x0100);
g_show_highscore_calls++;
}
void tdkpin_refresh_collision_record(uint16_t caller_bp, uint16_t id)
{
assert(caller_bp == 0x0100);
assert(id == g_collision_refresh_calls + 1);
g_collision_refresh_calls++;
}
void tdkpin_refresh_player_item(uint16_t caller_bp, uint16_t item)
{
assert(caller_bp == 0x0100);
assert(item == g_item_refresh_calls + 1);
g_item_refresh_calls++;
}
void tdkpin_restore_record_or_item_a(uint16_t caller_bp, uint16_t id)
{
assert(caller_bp == 0x0100);
assert(id == 1001 + g_restore_item_calls);
g_restore_item_calls++;
}
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_draw_item_status(int16_t item, HDC16 dc)
{
assert(dc == 0x3333);
g_last_item_count = (uint16_t)item;
g_draw_item_calls++;
}
void tdkpin_update_status_display(uint8_t player, HDC16 dc)
{
assert(dc == 0x3333);
g_last_status_player = player;
g_status_calls++;
}
void tdkpin_redraw_all_players(uint16_t caller_bp)
{
assert(caller_bp == 0x0100);
g_redraw_calls++;
}
void tdkpin_reset_ball_state(uint16_t caller_bp)
{
assert(caller_bp == 0x0100);
g_reset_ball_calls++;
}
void tdkpin_restore_background_region_b(
INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc)
{
assert(height == 20 && width == 148 && y == 243 && x == 84);
assert(dc == 0x3333);
g_restore_b_calls++;
}
void tdkpin_present_background_region(
INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc)
{
assert(height == 20 && width == 148 && y == 243 && x == 84);
assert(dc == 0x3333);
g_present_calls++;
}
void tdkpin_refresh_player_progress_markers(uint16_t caller_bp)
{
assert(caller_bp == 0x0100);
g_marker_refresh_calls++;
}
_Noreturn void win16_integer_divide_fault(void)
{
__builtin_trap();
}
static void reset_counters(void)
{
g_restore_previous_calls = 0;
g_restore_record_b_calls = 0;
g_dynamic_record_calls = 0;
g_sound_calls = 0;
g_load_highscores_calls = 0;
g_insert_score_calls = 0;
g_transition_calls = 0;
g_show_highscore_calls = 0;
g_collision_refresh_calls = 0;
g_item_refresh_calls = 0;
g_restore_item_calls = 0;
g_draw_item_calls = 0;
g_status_calls = 0;
g_redraw_calls = 0;
g_reset_ball_calls = 0;
g_restore_b_calls = 0;
g_present_calls = 0;
g_marker_refresh_calls = 0;
g_get_dc_calls = 0;
g_release_dc_calls = 0;
g_last_item_count = 0;
g_last_status_player = 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_player_count = 3;
g_current_player = 1;
g_ball_x_milli = 0;
g_ball_y_milli = 0;
reset_counters();
}
static Win16FarPtr record(uint16_t id)
{
return win16_dgroup_pointer((uint16_t)(0x095b + id * 0x53u));
}
static void test_special_respawn(void)
{
prepare_fixture();
g_current_player = 2;
win16_write_u16(win16_dgroup_pointer(0x399a), 0, 7);
win16_write_u32(win16_dgroup_pointer(0x3969), 0, 0x11112222u);
win16_write_u32(win16_dgroup_pointer(0x396d), 0, 0x33334444u);
win16_write_u8(win16_far_add_offset(g_receiver, 0x04a3), 1);
win16_write_u32(g_receiver, 0x56, 1000);
win16_write_u32(g_receiver, 0x0bca, 0xaaaaaaaau);
tdkpin_finish_ball_or_advance_player(0x0100);
assert(g_restore_previous_calls == 1);
assert(g_restore_record_b_calls == 1 && g_dynamic_record_calls == 1);
assert((uint32_t)g_ball_x_milli == 0x11112222u);
assert((uint32_t)g_ball_y_milli == 0x33334444u);
assert(win16_read_u32(win16_far_add_offset(g_receiver, 0x0bc2)) ==
0x11112222u);
assert(win16_read_u32(win16_far_add_offset(g_receiver, 0x0bc6)) ==
0x33334444u);
assert(win16_read_u8(win16_far_add_offset(g_receiver, 0x04a3)) == 0);
assert(win16_read_u32(win16_far_add_offset(g_receiver, 0x0baa)) == 0);
assert(win16_read_u32(win16_far_add_offset(g_receiver, 0x0bae)) == 800);
assert(win16_read_u32(win16_far_add_offset(g_receiver, 0x0bca)) == 0);
assert(win16_read_u16(win16_dgroup_pointer(0x399a)) == 0);
assert(win16_read_u8(win16_dgroup_pointer(0x0875)) == 1);
assert(win16_read_u8(win16_dgroup_pointer(0x41f9)) == 1);
assert(win16_read_u8(win16_far_add_offset(g_receiver, 0x61)) == 1);
assert(g_sound_calls == 0 && g_redraw_calls == 0);
}
static void seed_collision_and_player_three_mirrors(void)
{
for (uint16_t id = 1; id <= 175; id++) {
win16_write_u8(
win16_far_add_offset(record(id), 0x34), (uint8_t)(id & 1u));
win16_write_u16(record(id), 0x43, (uint16_t)(0x1000 + id));
win16_write_u8(
win16_far_add_offset(g_receiver, (uint16_t)(
0x0061 + 3u * 0x00afu + id)),
(uint8_t)(0x80u | (id & 1u)));
win16_write_u16(
g_receiver,
(uint16_t)(0x036c + 3u * 0x015eu + id * 2u),
(uint16_t)(0x2000 + id));
}
}
static void test_advance_to_live_player(void)
{
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x0874), 1);
win16_write_u16(win16_dgroup_pointer(0x0821), 0, 2);
win16_write_u16(win16_dgroup_pointer(0x0823), 0, 0);
win16_write_u16(win16_dgroup_pointer(0x0825), 0, 3);
win16_write_u8(win16_dgroup_pointer(0x0819), 4);
seed_collision_and_player_three_mirrors();
for (uint16_t item = 1; item <= 3; item++) {
win16_write_u8(win16_far_add_offset(
g_receiver, (uint16_t)(0x046c + 3u * 0x14u + item)), 1);
}
tdkpin_finish_ball_or_advance_player(0x0100);
assert(g_sound_calls == 1 && g_restore_previous_calls == 1);
assert(g_current_player == 3);
for (uint16_t id = 1; id <= 175; id++) {
assert(win16_read_u8(win16_far_add_offset(
g_receiver, (uint16_t)(0x0061 + 0x00afu + id))) ==
(uint8_t)(id & 1u));
assert(win16_read_u16(win16_far_add_offset(
g_receiver,
(uint16_t)(0x036c + 0x015eu + id * 2u))) ==
(uint16_t)(0x1000 + id));
assert(win16_read_u8(win16_far_add_offset(record(id), 0x34)) ==
(uint8_t)(0x80u | (id & 1u)));
assert(win16_read_u16(win16_far_add_offset(record(id), 0x43)) ==
(uint16_t)(0x2000 + id));
}
assert(g_collision_refresh_calls == 175 && g_item_refresh_calls == 20);
assert(g_restore_item_calls == 3);
assert(g_draw_item_calls == 1 && g_last_item_count == 3);
assert(g_status_calls == 1 && g_last_status_player == 3);
assert(g_redraw_calls == 1 && g_reset_ball_calls == 1);
assert(win16_read_u16(win16_dgroup_pointer(0x0825)) == 2);
assert(g_restore_b_calls == 1 && g_present_calls == 1);
assert(g_marker_refresh_calls == 1);
assert(g_get_dc_calls == 2 && g_release_dc_calls == 2);
}
static void test_game_over_path(void)
{
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x0874), 1);
win16_write_u16(win16_dgroup_pointer(0x0821), 0, 0);
win16_write_u16(win16_dgroup_pointer(0x0823), 0, 0);
g_player_count = 2;
tdkpin_finish_ball_or_advance_player(0x0100);
assert(g_load_highscores_calls == 1 && g_insert_score_calls == 1);
assert(g_current_player == 0 && g_transition_calls == 1);
assert(g_show_highscore_calls == 1);
assert(g_collision_refresh_calls == 0 && g_redraw_calls == 0);
assert(win16_read_u8(win16_far_add_offset(g_receiver, 0x0bda)) == 0);
assert(win16_read_u8(win16_far_add_offset(g_receiver, 0x0bdb)) == 0);
}
int main(void)
{
test_special_respawn();
test_advance_to_live_player();
test_game_over_path();
return 0;
}