A full comparison against the 175-record ledger found stale guessed coordinates in both relative slingshot chains. Circles 56/58/71/73 and lines 57/59/72/74 were displaced by up to 70 pixels. The corrected record-57 geometry then exposed a second issue: readable C and Rust had collapsed two independent binary response projections into one. Transcribe the accumulated initializer coordinates exactly. Use the swapped-component projection only to sign the current tangential response `abs(normal_velocity) * response_tangent`, and use the standard dot product for `spin_delta * 0.02 * response_tangent`. Correct the readable C evidence and Rust together, extend live tracing with the six-byte spin field, and seal the stopped-Wine transition from `(75530,354765)/(3000,15)` to `(77369,356597)/(1839,1832)` with Real48 spin `-5.28` in both harnesses. Test Plan: - stopped Wine 11.15 record-57 injection/trace -- matched position, velocity, and spin - `cargo test --workspace --all-targets --all-features` -- 123 passed - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed - `bash original/tools/test_reconstructed_c.sh` -- passed - `python3 original/tools/audit_reconstruction.py --require-complete` -- passed with zero incomplete or unclassified units - `python3 -m py_compile original/tools/trace_original_state.py original/tools/inject_original_state.py` -- passed - `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed - `git diff --cached --check` -- passed
531 lines
16 KiB
C
531 lines
16 KiB
C
#include "../tdkpin_physics.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_window;
|
|
static unsigned g_finish_calls;
|
|
static unsigned g_render_calls;
|
|
static unsigned g_update_calls;
|
|
static int32_t g_last_update;
|
|
static unsigned g_restore_b_calls;
|
|
static uint16_t g_last_restore_b;
|
|
static unsigned g_event_calls;
|
|
static uint16_t g_last_event_id;
|
|
static uint16_t g_last_event_flags;
|
|
static unsigned g_sound_calls;
|
|
static uint16_t g_last_sound;
|
|
static int32_t g_score_total;
|
|
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_finish_ball_or_advance_player(uint16_t caller_bp)
|
|
{
|
|
assert(caller_bp == 0x0300);
|
|
g_finish_calls++;
|
|
}
|
|
|
|
void tdkpin_render_ball_swept_region(uint16_t caller_bp)
|
|
{
|
|
assert(caller_bp == 0x0300);
|
|
g_render_calls++;
|
|
}
|
|
|
|
void tdkpin_update_dynamic_ball_record(
|
|
uint16_t caller_bp, int32_t displacement)
|
|
{
|
|
assert(caller_bp == 0x0300);
|
|
g_update_calls++;
|
|
g_last_update = displacement;
|
|
}
|
|
|
|
void tdkpin_restore_record_b(uint16_t caller_bp, uint16_t id)
|
|
{
|
|
assert(caller_bp == 0x0300);
|
|
g_restore_b_calls++;
|
|
g_last_restore_b = id;
|
|
}
|
|
|
|
void tdkpin_restore_record_or_item_a(uint16_t caller_bp, uint16_t id)
|
|
{
|
|
(void)caller_bp;
|
|
(void)id;
|
|
}
|
|
|
|
void tdkpin_handle_collision_events(
|
|
uint16_t caller_bp, uint16_t event_flags, uint16_t record_id)
|
|
{
|
|
assert(caller_bp == 0x0300);
|
|
g_event_calls++;
|
|
g_last_event_flags = event_flags;
|
|
g_last_event_id = record_id;
|
|
}
|
|
|
|
void tdkpin_play_sound_if_not_tilted(uint16_t sound_number)
|
|
{
|
|
g_sound_calls++;
|
|
g_last_sound = sound_number;
|
|
}
|
|
|
|
void tdkpin_add_score_from_caller_frame(uint16_t caller_bp, int32_t delta)
|
|
{
|
|
assert(caller_bp == 0x0300);
|
|
g_score_total += delta;
|
|
}
|
|
|
|
void tdkpin_draw_score_from_caller_frame(
|
|
uint16_t caller_bp, uint16_t value_low, uint16_t value_high)
|
|
{
|
|
(void)caller_bp;
|
|
(void)value_low;
|
|
(void)value_high;
|
|
}
|
|
|
|
void tdkpin_refresh_player_progress_markers(uint16_t caller_bp)
|
|
{
|
|
(void)caller_bp;
|
|
}
|
|
|
|
void tdkpin_apply_player_sprite_effect(
|
|
uint16_t caller_bp, int16_t argument)
|
|
{
|
|
(void)caller_bp;
|
|
(void)argument;
|
|
}
|
|
|
|
void tdkpin_composite_player_sprite(uint16_t sprite_index, HDC16 reference_dc)
|
|
{
|
|
(void)sprite_index;
|
|
(void)reference_dc;
|
|
}
|
|
|
|
void tdkpin_present_background_region(
|
|
INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc)
|
|
{
|
|
(void)height;
|
|
(void)width;
|
|
(void)y;
|
|
(void)x;
|
|
(void)dc;
|
|
}
|
|
|
|
HDC16 GetDC16(HWND16 window)
|
|
{
|
|
(void)window;
|
|
return 0x3333;
|
|
}
|
|
|
|
INT16 ReleaseDC16(HWND16 window, HDC16 dc)
|
|
{
|
|
(void)window;
|
|
(void)dc;
|
|
return 1;
|
|
}
|
|
|
|
void tdkpin_load_ball_slot(uint16_t caller_bp, uint16_t slot)
|
|
{
|
|
(void)caller_bp;
|
|
(void)slot;
|
|
g_load_calls++;
|
|
}
|
|
|
|
void tdkpin_save_ball_slot(uint16_t caller_bp, uint16_t slot)
|
|
{
|
|
(void)caller_bp;
|
|
(void)slot;
|
|
g_save_calls++;
|
|
}
|
|
|
|
uint16_t borland_random_below(uint16_t upper_bound)
|
|
{
|
|
assert(upper_bound == 4);
|
|
return 0;
|
|
}
|
|
|
|
BorlandReal48 borland_random_real48_registers(void)
|
|
{
|
|
return (BorlandReal48){{0x80,0,0,0,0,0}};
|
|
}
|
|
|
|
static bool in_bounds(
|
|
int32_t x,
|
|
int32_t y,
|
|
int32_t maximum_y,
|
|
int32_t maximum_x,
|
|
int32_t minimum_y,
|
|
int32_t minimum_x)
|
|
{
|
|
return x >= minimum_x && x <= maximum_x &&
|
|
y >= minimum_y && y <= maximum_y;
|
|
}
|
|
|
|
uint16_t tdkpin_predicted_ball_in_bounds(
|
|
uint16_t ignored_static_link,
|
|
int32_t maximum_y,
|
|
int32_t maximum_x,
|
|
int32_t minimum_y,
|
|
int32_t minimum_x)
|
|
{
|
|
(void)ignored_static_link;
|
|
return in_bounds(
|
|
(int32_t)win16_read_u32(win16_dgroup_pointer(0x07db)),
|
|
(int32_t)win16_read_u32(win16_dgroup_pointer(0x07df)),
|
|
maximum_y, maximum_x, minimum_y, minimum_x);
|
|
}
|
|
|
|
uint16_t tdkpin_receiver_point_in_bounds(
|
|
uint16_t caller_bp,
|
|
int32_t maximum_y,
|
|
int32_t maximum_x,
|
|
int32_t minimum_y,
|
|
int32_t minimum_x)
|
|
{
|
|
assert(caller_bp == 0x0300);
|
|
return in_bounds(
|
|
(int32_t)win16_read_u32(win16_far_add_offset(g_window, 0x0bba)),
|
|
(int32_t)win16_read_u32(win16_far_add_offset(g_window, 0x0bbe)),
|
|
maximum_y, maximum_x, minimum_y, minimum_x);
|
|
}
|
|
|
|
static Win16FarPtr record_pointer(uint16_t id)
|
|
{
|
|
return win16_dgroup_pointer((uint16_t)(
|
|
0x095b + id * sizeof(TdkpinCollisionRecord)));
|
|
}
|
|
|
|
static void write_record(
|
|
uint16_t id, const TdkpinCollisionRecord *record)
|
|
{
|
|
const uint8_t *bytes = (const uint8_t *)record;
|
|
for (uint16_t index = 0; index < sizeof(*record); index++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(record_pointer(id), index), bytes[index]);
|
|
}
|
|
}
|
|
|
|
static TdkpinCollisionRecord base_record(uint8_t type)
|
|
{
|
|
TdkpinCollisionRecord record = {0};
|
|
record.type = type;
|
|
record.subtype = 1;
|
|
record.active = 1;
|
|
record.bounds_min_x_milli = 0;
|
|
record.bounds_min_y_milli = 0;
|
|
record.bounds_max_x_milli = 200000;
|
|
record.bounds_max_y_milli = 200000;
|
|
record.layer_mask = 1;
|
|
return record;
|
|
}
|
|
|
|
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, 0);
|
|
g_window = win16_make_far_pointer(0x7000, 0);
|
|
win16_write_stack_u16(0x0306, win16_far_offset(g_window));
|
|
win16_write_stack_u16(0x0308, win16_far_selector(g_window));
|
|
win16_write_u16(g_window, 4, 0x2222);
|
|
win16_write_u8(win16_far_add_offset(g_window, 0x61), 1);
|
|
win16_write_u8(win16_far_add_offset(g_window, 0x0bdd), 1);
|
|
win16_write_u16(win16_dgroup_pointer(0x0871), 0, 1);
|
|
win16_write_u32(g_window, 0x56, 10000);
|
|
g_finish_calls = g_render_calls = g_update_calls = 0;
|
|
g_restore_b_calls = g_event_calls = g_sound_calls = 0;
|
|
g_score_total = 0;
|
|
g_load_calls = g_save_calls = 0;
|
|
}
|
|
|
|
static void set_ball(
|
|
int32_t x, int32_t y, int32_t velocity_x, int32_t velocity_y)
|
|
{
|
|
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_window, 0x0baa, (uint32_t)velocity_x);
|
|
win16_write_u32(g_window, 0x0bae, (uint32_t)velocity_y);
|
|
}
|
|
|
|
static void test_free_integration_and_speed_clamp(void)
|
|
{
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 1000, 2000);
|
|
win16_write_u32(g_window, 0x5a, 100);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d3)) == 101000);
|
|
assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d7)) == 102100);
|
|
assert(g_update_calls == 1 && g_last_update == 1);
|
|
assert(g_render_calls == 1);
|
|
assert(win16_read_u32(win16_far_add_offset(g_window, 0x4d)) == 1);
|
|
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 3000, 4000);
|
|
win16_write_u32(g_window, 0x56, 1000);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0baa)) == 600);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0bae)) == 800);
|
|
}
|
|
|
|
static void test_circle_and_segment_collisions(void)
|
|
{
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 1000, 0);
|
|
TdkpinCollisionRecord circle = base_record(1);
|
|
circle.point1_x_milli = 103000;
|
|
circle.point1_y_milli = 100000;
|
|
circle.radius_milli = borland_i32_to_real48(4000);
|
|
write_record(1, &circle);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0baa)) == 0);
|
|
assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d3)) == 100000);
|
|
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 0, 1000);
|
|
TdkpinCollisionRecord segment = base_record(2);
|
|
segment.point1_x_milli = 50000;
|
|
segment.point1_y_milli = 101000;
|
|
segment.point2_x_milli = 150000;
|
|
segment.point2_y_milli = 101000;
|
|
write_record(1, &segment);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0bae)) == 0);
|
|
assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d7)) == 100000);
|
|
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 0, 1000);
|
|
segment = base_record(2);
|
|
segment.point1_x_milli = 50000;
|
|
segment.point1_y_milli = 101000;
|
|
segment.point2_x_milli = 150000;
|
|
segment.point2_y_milli = 101000;
|
|
segment.response_tangent = (BorlandReal48){{0x7d,0xcd,0xcc,0xcc,0xcc,0x4c}};
|
|
write_record(1, &segment);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0baa)) == -100);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0bae)) == 0);
|
|
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 1000, 0);
|
|
circle = base_record(1);
|
|
circle.point1_x_milli = 103000;
|
|
circle.point1_y_milli = 100000;
|
|
circle.radius_milli = borland_i32_to_real48(4000);
|
|
circle.response_normal = borland_i32_to_real48(1);
|
|
circle.flags = 0x0020;
|
|
circle.score_low = 100;
|
|
write_record(1, &circle);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0baa)) == -1000);
|
|
assert(g_event_calls == 1 && g_last_event_flags == 0x0020);
|
|
assert(g_score_total == 100);
|
|
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 0, 1000);
|
|
segment = base_record(2);
|
|
segment.point1_x_milli = 50000;
|
|
segment.point1_y_milli = 101000;
|
|
segment.point2_x_milli = 150000;
|
|
segment.point2_y_milli = 101000;
|
|
write_record(1, &segment);
|
|
segment.point1_y_milli = 100500;
|
|
segment.point2_y_milli = 100500;
|
|
segment.response_normal = borland_i32_to_real48(1);
|
|
write_record(2, &segment);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0bae)) == 0);
|
|
}
|
|
|
|
static void test_capture_and_trigger_records(void)
|
|
{
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 0, 0);
|
|
TdkpinCollisionRecord capture = base_record(3);
|
|
capture.point1_x_milli = 100000;
|
|
capture.point1_y_milli = 100000;
|
|
capture.radius_milli = borland_i32_to_real48(20000);
|
|
write_record(50, &capture);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert(win16_read_u16(win16_far_add_offset(record_pointer(50), 0x43)) == 1);
|
|
assert(win16_read_u32(win16_far_add_offset(g_window, 0x0bca)) == 5);
|
|
assert(g_sound_calls == 1 && g_last_sound == 15);
|
|
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 1000, 0);
|
|
TdkpinCollisionRecord trigger = base_record(4);
|
|
trigger.point1_x_milli = 0;
|
|
trigger.point1_y_milli = 0;
|
|
trigger.point2_x_milli = 200000;
|
|
trigger.point2_y_milli = 200000;
|
|
write_record(6, &trigger);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0baa)) == 900);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0bae)) == -8500);
|
|
assert(g_restore_b_calls == 1 && g_last_restore_b == 6);
|
|
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 1000, 0);
|
|
win16_write_u8(win16_dgroup_pointer(0x07d1), 1);
|
|
win16_write_u8(win16_dgroup_pointer(0x081b), 2);
|
|
trigger = base_record(4);
|
|
trigger.point1_x_milli = 101000;
|
|
trigger.point1_y_milli = 100000;
|
|
trigger.radius_milli = borland_i32_to_real48(5000);
|
|
trigger.flags = 0x0040;
|
|
trigger.score_low = 100;
|
|
write_record(10, &trigger);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((win16_read_u16(
|
|
win16_far_add_offset(record_pointer(10), 0x41)) & 0x4000) != 0);
|
|
assert(g_sound_calls == 1 && g_last_sound == 4);
|
|
assert(g_event_calls == 1 && g_last_event_id == 10 &&
|
|
g_last_event_flags == 0x4040);
|
|
assert(g_score_total == 200);
|
|
}
|
|
|
|
static void test_capture_boundary_and_claw(void)
|
|
{
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 1000, 0);
|
|
TdkpinCollisionRecord capture = base_record(3);
|
|
capture.point1_x_milli = 103000;
|
|
capture.point1_y_milli = 102000;
|
|
capture.radius_milli = borland_i32_to_real48(3800);
|
|
capture.contact_state = 99;
|
|
capture.flags = 0x0010;
|
|
write_record(50, &capture);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert(g_event_calls == 1 && g_last_event_id == 50);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0baa)) != 1000);
|
|
|
|
prepare_fixture();
|
|
set_ball(100000, 100000, 0, 0);
|
|
capture = base_record(3);
|
|
capture.point1_x_milli = 100000;
|
|
capture.point1_y_milli = 100000;
|
|
capture.radius_milli = borland_i32_to_real48(20000);
|
|
capture.contact_state = 1;
|
|
write_record(89, &capture);
|
|
win16_write_u32(g_window, 0x0bca, 300);
|
|
for (uint16_t id = 12; id <= 20; id++) {
|
|
TdkpinCollisionRecord gate = base_record(4);
|
|
write_record(id, &gate);
|
|
}
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert(win16_read_u8(win16_dgroup_pointer(0x07ca)) == 1);
|
|
assert(win16_read_u16(win16_dgroup_pointer(0x086f)) == 1);
|
|
assert(win16_read_u8(win16_dgroup_pointer(0x0873)) == 1);
|
|
assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdf)) == 1);
|
|
for (uint16_t id = 12; id <= 20; id++) {
|
|
assert(win16_read_u8(
|
|
win16_far_add_offset(record_pointer(id), 0x34)) == 0);
|
|
}
|
|
}
|
|
|
|
static void test_out_of_bounds_and_zero_substeps_multiball(void)
|
|
{
|
|
prepare_fixture();
|
|
set_ball(340000, 100000, 1001, 0);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert(g_finish_calls == 1);
|
|
|
|
prepare_fixture();
|
|
win16_write_u8(win16_far_add_offset(g_window, 0x61), 2);
|
|
win16_write_u16(win16_dgroup_pointer(0x0871), 0, 0);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert(g_load_calls == 2 && g_save_calls == 2);
|
|
assert(g_render_calls == 2 && g_update_calls == 0);
|
|
}
|
|
|
|
static void test_live_relative_record_57_transition(void)
|
|
{
|
|
prepare_fixture();
|
|
set_ball(72530, 354750, 3000, 0);
|
|
win16_write_u32(g_window, 0x5a, 15);
|
|
win16_write_u32(g_window, 0x56, 3800);
|
|
TdkpinCollisionRecord segment = base_record(2);
|
|
segment.bounds_min_x_milli = 45000;
|
|
segment.bounds_min_y_milli = 335000;
|
|
segment.bounds_max_x_milli = 102000;
|
|
segment.bounds_max_y_milli = 371000;
|
|
segment.point1_x_milli = 97000;
|
|
segment.point1_y_milli = 366000;
|
|
segment.point2_x_milli = 50000;
|
|
segment.point2_y_milli = 340000;
|
|
segment.response_normal =
|
|
(BorlandReal48){{0x80,0x00,0x00,0x00,0x00,0x00}};
|
|
segment.response_tangent =
|
|
(BorlandReal48){{0x7d,0xcd,0xcc,0xcc,0xcc,0x4c}};
|
|
segment.layer_mask = 2;
|
|
write_record(57, &segment);
|
|
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_dgroup_pointer(0x07d3)) == 75530);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_dgroup_pointer(0x07d7)) == 354765);
|
|
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_dgroup_pointer(0x07d3)) == 77369);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_dgroup_pointer(0x07d7)) == 356597);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0baa)) == 1839);
|
|
assert((int32_t)win16_read_u32(
|
|
win16_far_add_offset(g_window, 0x0bae)) == 1832);
|
|
static const uint8_t expected_spin[6] = {
|
|
0x83, 0x5d, 0x8f, 0xc2, 0xf5, 0xa8};
|
|
for (uint16_t index = 0; index < 6; index++) {
|
|
assert(win16_read_u8(win16_far_add_offset(
|
|
g_window, (uint16_t)(0x0bce + index))) ==
|
|
expected_spin[index]);
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_free_integration_and_speed_clamp();
|
|
test_circle_and_segment_collisions();
|
|
test_capture_and_trigger_records();
|
|
test_capture_boundary_and_claw();
|
|
test_out_of_bounds_and_zero_substeps_multiball();
|
|
test_live_relative_record_57_transition();
|
|
return 0;
|
|
}
|