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

539 lines
17 KiB
C

#include "../tdkpin_timer_tick.h"
#include "../tdkpin_borland_runtime.h"
#include "../tdkpin_collision_records.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
HDC16 destination;
INT16 x;
INT16 y;
INT16 width;
INT16 height;
HDC16 source;
INT16 source_x;
INT16 source_y;
uint32_t raster_operation;
} BlitCall;
static uint8_t g_dgroup[0x5000];
static uint8_t g_objects[0x1000];
static uint8_t g_stack[0x1000];
static Win16FarPtr g_window;
static uint16_t g_refresh_ids[200];
static size_t g_refresh_count;
static uint16_t g_restore_a_ids[32];
static size_t g_restore_a_id_count;
static uint16_t g_restore_b_ids[32];
static size_t g_restore_b_id_count;
static uint16_t g_save_slots[8];
static size_t g_save_count;
static uint16_t g_load_slots[8];
static size_t g_load_count;
static int32_t g_dynamic_records[8];
static size_t g_dynamic_count;
static uint16_t g_sounds[8];
static size_t g_sound_count;
static uint8_t g_claw_alternate;
static uint16_t g_claw_frame;
static unsigned g_claw_calls;
static unsigned g_render_ball_calls;
static unsigned g_rotate_calls;
static unsigned g_restore_previous_calls;
static unsigned g_finish_calls;
static unsigned g_simulate_calls;
static unsigned g_reset_calls;
static unsigned g_flipper_calls;
static unsigned g_drain_calls;
static unsigned g_initialize_calls;
static unsigned g_redraw_calls;
static unsigned g_progress_calls;
static unsigned g_panel_calls;
static int16_t g_last_item;
static unsigned g_item_calls;
static unsigned g_region_a_calls;
static unsigned g_region_b_calls;
static unsigned g_present_calls;
static BlitCall g_blits[16];
static size_t g_blit_count;
static unsigned g_get_dc_calls;
static unsigned g_release_dc_calls;
static unsigned g_create_dc_calls;
static unsigned g_create_bitmap_calls;
static unsigned g_select_calls;
static unsigned g_delete_object_calls;
static unsigned g_delete_dc_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_refresh_collision_record(uint16_t caller_bp, uint16_t id)
{
assert(caller_bp == 0x0300);
g_refresh_ids[g_refresh_count++] = id;
}
void tdkpin_restore_record_or_item_a(uint16_t caller_bp, uint16_t id)
{
assert(caller_bp == 0x0300);
g_restore_a_ids[g_restore_a_id_count++] = id;
}
void tdkpin_restore_record_b(uint16_t caller_bp, uint16_t id)
{
assert(caller_bp == 0x0300);
g_restore_b_ids[g_restore_b_id_count++] = id;
}
void tdkpin_save_ball_slot(uint16_t caller_bp, uint16_t slot)
{
assert(caller_bp == 0x0300);
g_save_slots[g_save_count++] = slot;
}
void tdkpin_load_ball_slot(uint16_t caller_bp, uint16_t slot)
{
assert(caller_bp == 0x0300);
g_load_slots[g_load_count++] = slot;
}
void tdkpin_update_dynamic_ball_record(
uint16_t caller_bp, int32_t displacement)
{
assert(caller_bp == 0x0300);
g_dynamic_records[g_dynamic_count++] = displacement;
}
void tdkpin_play_sound_if_not_tilted(uint16_t sound_number)
{
g_sounds[g_sound_count++] = sound_number;
}
void tdkpin_draw_claw_frame(
uint8_t alternate, uint16_t frame, HDC16 destination)
{
assert(destination == 0x3333);
g_claw_alternate = alternate;
g_claw_frame = frame;
g_claw_calls++;
}
void tdkpin_render_ball_from_caller_frame(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_render_ball_calls++;
}
void tdkpin_rotate_and_draw_targets(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_rotate_calls++;
}
void tdkpin_restore_previous_ball_region(
uint16_t caller_bp, uint8_t suppress_present)
{
assert(caller_bp == 0x0300 && suppress_present == 0);
g_restore_previous_calls++;
}
void tdkpin_finish_ball_or_advance_player(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_finish_calls++;
}
void tdkpin_simulate_ball_and_dispatch_collision(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_simulate_calls++;
}
void tdkpin_reset_ball_state(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_reset_calls++;
}
void tdkpin_update_flippers(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_flipper_calls++;
}
void tdkpin_drain_messages_except_input(void)
{
g_drain_calls++;
}
void tdkpin_initialize_player_state(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_initialize_calls++;
}
void tdkpin_redraw_all_players(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_redraw_calls++;
}
void tdkpin_refresh_player_progress_markers(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_progress_calls++;
}
void tdkpin_draw_panel_animation(uint16_t caller_bp)
{
assert(caller_bp == 0x0300);
g_panel_calls++;
}
void tdkpin_draw_item_status(int16_t item, HDC16 destination)
{
assert(destination == 0x3333);
g_last_item = item;
g_item_calls++;
}
void tdkpin_restore_background_region_a(
INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc)
{
(void)height;
(void)width;
(void)y;
(void)x;
assert(dc == 0x3333);
g_region_a_calls++;
}
void tdkpin_restore_background_region_b(
INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc)
{
(void)height;
(void)width;
(void)y;
(void)x;
assert(dc == 0x3333);
g_region_b_calls++;
}
void tdkpin_present_background_region(
INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc)
{
(void)height;
(void)width;
(void)y;
(void)x;
assert(dc == 0x3333);
g_present_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;
}
HDC16 CreateCompatibleDC16(HDC16 dc)
{
assert(dc == 0x3333);
g_create_dc_calls++;
return g_create_dc_calls == 1 ? 0x4444 : 0x5555;
}
HBITMAP16 CreateCompatibleBitmap16(HDC16 dc, INT16 width, INT16 height)
{
assert(dc == 0x3333 && width == 30 && height == 284);
g_create_bitmap_calls++;
return 0x6666;
}
HGDIOBJ16 SelectObject16(HDC16 dc, HGDIOBJ16 object)
{
assert(dc == 0x4444 || dc == 0x5555);
(void)object;
g_select_calls++;
return (HGDIOBJ16)(0x7000 + g_select_calls);
}
BOOL16 BitBlt16(
HDC16 destination,
INT16 x,
INT16 y,
INT16 width,
INT16 height,
HDC16 source,
INT16 source_x,
INT16 source_y,
uint32_t raster_operation)
{
g_blits[g_blit_count++] = (BlitCall){
destination, x, y, width, height,
source, source_x, source_y, raster_operation,
};
return 1;
}
BOOL16 DeleteObject16(HGDIOBJ16 object)
{
assert(object == 0x6666);
g_delete_object_calls++;
return 1;
}
BOOL16 DeleteDC16(HDC16 dc)
{
assert(dc == 0x4444 || dc == 0x5555);
g_delete_dc_calls++;
return 1;
}
static Win16FarPtr record(uint16_t id)
{
return win16_dgroup_pointer((uint16_t)(
0x095b + id * sizeof(TdkpinCollisionRecord)));
}
static void prepare_fixture(void)
{
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, 0x0300);
g_window = win16_make_far_pointer(0x7000, 0);
win16_write_u16(g_window, 4, 0x2222);
win16_write_u8(win16_dgroup_pointer(0x07c4), 1);
memset(g_refresh_ids, 0, sizeof(g_refresh_ids));
memset(g_restore_a_ids, 0, sizeof(g_restore_a_ids));
memset(g_restore_b_ids, 0, sizeof(g_restore_b_ids));
g_refresh_count = g_restore_a_id_count = g_restore_b_id_count = 0;
g_save_count = g_load_count = g_dynamic_count = g_sound_count = 0;
g_claw_calls = g_render_ball_calls = g_rotate_calls = 0;
g_restore_previous_calls = g_finish_calls = g_simulate_calls = 0;
g_reset_calls = g_flipper_calls = g_drain_calls = 0;
g_initialize_calls = g_redraw_calls = g_progress_calls = 0;
g_panel_calls = g_item_calls = 0;
g_region_a_calls = g_region_b_calls = g_present_calls = 0;
g_blit_count = g_get_dc_calls = g_release_dc_calls = 0;
g_create_dc_calls = g_create_bitmap_calls = g_select_calls = 0;
g_delete_object_calls = g_delete_dc_calls = 0;
}
static void test_guard_and_refresh_epilogue(void)
{
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x07c4), 0);
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(g_flipper_calls == 0 && g_drain_calls == 0);
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x07c5), 1);
win16_write_u16(g_window, 0x5e, 2);
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(g_refresh_count == 175);
assert(g_refresh_ids[0] == 1 && g_refresh_ids[174] == 175);
assert(win16_read_u8(win16_dgroup_pointer(0x07c5)) == 0);
assert(win16_read_u32(win16_dgroup_pointer(0x0876)) == 1);
assert(g_flipper_calls == 1 && g_drain_calls == 1);
assert(win16_read_u8(win16_far_add_offset(g_window, 0x51)) == 0);
assert(win16_read_u16(win16_far_add_offset(g_window, 0x5e)) == 1);
}
static void test_claw_release(void)
{
prepare_fixture();
win16_write_u8(win16_far_add_offset(g_window, 0x0bdd), 1);
win16_write_u8(win16_far_add_offset(g_window, 0x0bdf), 1);
win16_write_u8(win16_dgroup_pointer(0x07ca), 1);
win16_write_u16(win16_dgroup_pointer(0x086d), 0, 2);
win16_write_u16(win16_dgroup_pointer(0x086f), 0, 2);
win16_write_u32(g_window, 0x56, 1000);
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(g_claw_calls == 1 && g_claw_alternate == 0 && g_claw_frame == 2);
assert(win16_read_u16(win16_dgroup_pointer(0x086f)) == 10);
assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d3)) == 259000);
assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d7)) == 81000);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_window, 0x0baa)) == -450);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_window, 0x0bae)) == 50);
assert(g_save_count == 1 && g_save_slots[0] == 1);
assert(g_render_ball_calls == 1);
assert(g_sound_count == 1 && g_sounds[0] == 16);
assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdf)) == 0);
assert(g_simulate_calls == 1);
}
static void test_multiball_promotion(void)
{
prepare_fixture();
win16_write_u8(win16_far_add_offset(g_window, 0x0bdd), 1);
win16_write_u8(win16_far_add_offset(g_window, 0x0bdf), 1);
win16_write_u8(win16_far_add_offset(g_window, 0x61), 2);
win16_write_u8(win16_far_add_offset(g_window, 0x0be1), 1);
win16_write_u8(win16_dgroup_pointer(0x424c), 1);
win16_write_u32(win16_dgroup_pointer(0x07e7), 0, 0x11111111);
win16_write_u32(win16_dgroup_pointer(0x07ef), 0, 0x22222222);
win16_write_u32(win16_dgroup_pointer(0x07f7), 0, 0x33333333);
win16_write_u32(win16_dgroup_pointer(0x07ff), 0, 0x44444444);
win16_write_u32(win16_dgroup_pointer(0x0807), 0, 0x55555555);
for (uint16_t index = 0; index < 6; index++) {
win16_write_u8(
win16_dgroup_pointer((uint16_t)(0x0811 + index)),
(uint8_t)(0xa0 + index));
}
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(win16_read_u8(win16_far_add_offset(g_window, 0x61)) == 1);
assert(win16_read_u8(win16_dgroup_pointer(0x41f9)) == 1);
assert(win16_read_u8(win16_dgroup_pointer(0x424c)) == 0);
assert(win16_read_u32(win16_dgroup_pointer(0x07e3)) == 0x11111111);
assert(win16_read_u32(win16_dgroup_pointer(0x07eb)) == 0x22222222);
assert(win16_read_u32(win16_dgroup_pointer(0x07f3)) == 0x33333333);
assert(win16_read_u32(win16_dgroup_pointer(0x07fb)) == 0x44444444);
assert(win16_read_u32(win16_dgroup_pointer(0x0803)) == 0x55555555);
assert(g_load_count == 1 && g_load_slots[0] == 0);
assert(g_finish_calls == 0);
}
static void test_multiball_spawn(void)
{
prepare_fixture();
win16_write_u8(win16_far_add_offset(g_window, 0x0bdd), 1);
win16_write_u8(win16_far_add_offset(g_window, 0x0bdf), 1);
win16_write_u8(win16_far_add_offset(g_window, 0x0be0), 1);
win16_write_u8(win16_dgroup_pointer(0x07d1), 1);
win16_write_u32(win16_dgroup_pointer(0x3969), 0, 123000);
win16_write_u32(win16_dgroup_pointer(0x396d), 0, 456000);
win16_write_u32(g_window, 0x56, 1000);
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(g_save_count == 2 && g_save_slots[0] == 0 && g_save_slots[1] == 1);
assert(g_dynamic_count == 2 &&
g_dynamic_records[0] == 1 && g_dynamic_records[1] == 2);
assert(g_restore_previous_calls == 1);
assert(g_restore_b_id_count == 1 && g_restore_b_ids[0] == 1020);
assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d3)) == 123000);
assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d7)) == 456000);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_window, 0x0bae)) == 800);
assert(win16_read_u8(win16_far_add_offset(g_window, 0x61)) == 2);
assert(win16_read_u8(win16_dgroup_pointer(0x0874)) == 1);
assert(win16_read_u8(win16_far_add_offset(g_window, 0x0be0)) == 0);
}
static void test_intro_tick_32_and_marquee(void)
{
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x07c9), 1);
win16_write_u32(win16_dgroup_pointer(0x0876), 0, 31);
win16_write_u16(record(90), 0x4b, 10);
win16_write_u16(record(90), 0x4d, 20);
win16_write_u16(record(90), 0x4f, 50);
win16_write_u16(record(90), 0x51, 60);
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(g_restore_a_id_count == 5);
assert(g_restore_a_ids[0] == 140);
assert(g_restore_a_ids[1] == 1001);
assert(g_restore_a_ids[2] == 153 &&
g_restore_a_ids[3] == 154 && g_restore_a_ids[4] == 6);
assert(g_restore_b_id_count == 1 && g_restore_b_ids[0] == 1002);
assert(g_region_a_calls == 2 && g_region_b_calls == 1);
assert(g_present_calls == 3);
assert(g_item_calls == 1 && g_last_item == 0);
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x07c9), 1);
win16_write_u32(win16_dgroup_pointer(0x0876), 0, 5);
win16_write_u16(win16_dgroup_pointer(0x085f), 0, 0x7777);
win16_write_u16(win16_dgroup_pointer(0x0861), 0, 0x8888);
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(win16_read_u32(win16_dgroup_pointer(0x087a)) == 1);
assert(g_create_dc_calls == 2 && g_create_bitmap_calls == 1);
assert(g_blit_count == 4);
assert(g_blits[0].destination == 0x5555 &&
g_blits[0].width == 30 && g_blits[0].height == 100 &&
g_blits[0].source_x == 473 && g_blits[0].source_y == 277);
assert(g_blits[2].height == 237 && g_blits[2].source_x == 400);
assert(g_blits[3].destination == 0x3333 &&
g_blits[3].x == 233 && g_blits[3].y == 372);
assert(g_select_calls == 5 && g_delete_object_calls == 1);
assert(g_delete_dc_calls == 2);
}
static void test_panel_and_game_initialization(void)
{
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x07c8), 1);
win16_write_u32(win16_dgroup_pointer(0x0851), 0, 1);
for (uint16_t id = 129; id <= 133; id++) {
win16_write_u16(record(id), 0x43, 0xffff);
}
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdf)) == 1);
assert(win16_read_u32(win16_dgroup_pointer(0x0851)) == 2);
assert(g_panel_calls == 1);
for (uint16_t id = 129; id <= 133; id++) {
assert(win16_read_u16(win16_far_add_offset(record(id), 0x43)) == 0);
}
prepare_fixture();
win16_write_u8(win16_dgroup_pointer(0x07c8), 1);
win16_write_u32(win16_dgroup_pointer(0x0851), 0, 281);
win16_write_u8(win16_dgroup_pointer(0x07d1), 1);
for (uint16_t item = 15; item <= 19; item++) {
win16_write_u8(
win16_far_add_offset(g_window, (uint16_t)(0x47b + 20 + item)), 1);
}
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(win16_read_u8(win16_dgroup_pointer(0x07c8)) == 0);
assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdf)) == 0);
assert(win16_read_u32(win16_dgroup_pointer(0x0851)) == 0);
assert(g_region_b_calls == 1 && g_present_calls == 1);
prepare_fixture();
win16_write_u16(win16_dgroup_pointer(0x081f), 0, 0xffff);
tdkpin_timer_tick_with_frame(0x0300, g_window);
assert(g_initialize_calls == 1 && g_redraw_calls == 1);
assert(g_progress_calls == 1 && g_reset_calls == 1);
assert(g_region_b_calls == 1 && g_present_calls == 1);
assert(win16_read_u32(win16_dgroup_pointer(0x0876)) == 0);
}
int main(void)
{
test_guard_and_refresh_epilogue();
test_claw_release();
test_multiball_promotion();
test_multiball_spawn();
test_intro_tick_32_and_marquee();
test_panel_and_game_initialization();
return 0;
}