The clone's moving-flipper gate used the cross-product operands in the opposite order, mirroring and narrowing the hit wedge. Right-flipper release also used record 81's first endpoint even though the original reads its second endpoint. Correct both geometry paths, retain the recovered swept tip bounds, and add live-binary boundary vectors plus a dense transition regression. Test Plan: - `cargo test --workspace --all-targets --all-features` -- passed (134 tests) - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed - `cargo build --profile production` -- passed - `LSAN_OPTIONS=detect_leaks=0 ASAN_OPTIONS=detect_leaks=0 bash original/tools/test_reconstructed_c.sh` -- passed - `python3 original/tools/audit_reconstruction.py --require-complete` -- passed - `git diff --cached --check` -- passed
299 lines
9.2 KiB
C
299 lines
9.2 KiB
C
#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 == -5578 && g_move_y == 17127);
|
|
assert(g_sound_count == 1 && g_sounds[0] == 21);
|
|
assert(g_move_calls == 1);
|
|
}
|
|
|
|
static void test_live_binary_cross_product_order(void)
|
|
{
|
|
prepare_fixture();
|
|
win16_write_u32(g_receiver, 0x56, 3800);
|
|
set_ball(100000, 370000, 1000, 2000);
|
|
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_receiver, 0x0baa)) == -189);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_receiver, 0x0bae)) == 960);
|
|
assert(g_move_x == 0 && g_move_y == 0);
|
|
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, point2_x_milli),
|
|
240000);
|
|
write_i32(
|
|
81,
|
|
offsetof(TdkpinCollisionRecord, point2_y_milli),
|
|
411000);
|
|
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_live_binary_cross_product_order();
|
|
test_left_positive_contact();
|
|
test_right_negative_contact();
|
|
test_vertical_edge_fallbacks();
|
|
test_multiball_slot_loop_and_invalid_flipper();
|
|
test_gates();
|
|
return 0;
|
|
}
|