Files
tdkpin/original/reconstructed/tdkpin_physics.c
T
ddidderr f8dcceda5e fix(physics): restore binary record scan order
Interleave static ranges, type-three captures, type-four triggers, magnetic records, and dynamic ball records in exact ID order while tracking predicted position separately from mutable motion. Preserve type-three/dynamic broadphase and the raw stack quirk where fixed candidate slot one wins even when a later candidate is nearer; seal that behavior in both C and Rust harnesses.

Test Plan:
- bash original/tools/test_reconstructed_c.sh
- python3 original/tools/audit_reconstruction.py --require-complete
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- rumdl check original/C_RECONSTRUCTION_FINAL_AUDIT.md original/MECHANICS_PROGRESS.md tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md tdkpin-rs/README.md
- git diff --check
2026-08-23 18:46:44 +02:00

1141 lines
40 KiB
C

/* Ball substep integrator and collision dispatcher at 1000:c79c. */
#include "tdkpin_physics.h"
#include "tdkpin_arithmetic.h"
#include "tdkpin_collision_events.h"
#include "tdkpin_collision_records.h"
#include "tdkpin_gameplay_helpers.h"
#include "tdkpin_player_state.h"
#include "tdkpin_random.h"
#include "tdkpin_real48.h"
#include "tdkpin_render.h"
#include "tdkpin_sound.h"
#include "tdkpin_turn_state.h"
enum {
RECORDS_BASE = 0x095b,
RECORD_BYTES = 0x53,
MAX_RECORDS = 175,
};
#pragma pack(push, 1)
typedef struct {
uint16_t record_id;
BorlandReal48 normal_x;
BorlandReal48 normal_y;
int32_t normal_velocity;
BorlandReal48 response_normal;
BorlandReal48 response_tangent;
BorlandReal48 response_auxiliary;
BorlandReal48 response_kick;
uint16_t flags;
int32_t score;
int32_t surface_distance;
} PhysicsCollisionCandidate;
#pragma pack(pop)
_Static_assert(sizeof(PhysicsCollisionCandidate) == 0x34,
"1000:c79c candidate layout drift");
typedef struct {
int32_t velocity_x;
int32_t velocity_y;
BorlandReal48 spin;
int32_t speed;
} BallMotion;
static const BorlandReal48 g_real48_thousand =
{{0x8a,0,0,0,0,0x7a}};
static const BorlandReal48 g_real48_one =
{{0x81,0,0,0,0,0}};
static const BorlandReal48 g_real48_point_nine =
{{0x80,0x66,0x66,0x66,0x66,0x66}};
static const BorlandReal48 g_real48_point_six =
{{0x80,0x9a,0x99,0x99,0x99,0x19}};
static const BorlandReal48 g_real48_point_four =
{{0x7f,0xcd,0xcc,0xcc,0xcc,0x4c}};
static const BorlandReal48 g_real48_point_three =
{{0x7f,0x9a,0x99,0x99,0x99,0x19}};
static const BorlandReal48 g_real48_point_zero_two =
{{0x7b,0x71,0x3d,0x0a,0xd7,0x23}};
static const BorlandReal48 g_real48_twenty =
{{0x85,0,0,0,0,0x20}};
static Win16FarPtr caller_receiver(uint16_t caller_bp)
{
uint16_t offset = win16_read_stack_u16((uint16_t)(caller_bp + 6));
uint16_t selector = win16_read_stack_u16((uint16_t)(caller_bp + 8));
return win16_make_far_pointer(selector, offset);
}
static Win16FarPtr record_pointer(uint16_t id)
{
return win16_dgroup_pointer((uint16_t)(RECORDS_BASE + id * RECORD_BYTES));
}
static int32_t read_i32(Win16FarPtr base, uint16_t offset)
{
return (int32_t)win16_read_u32(win16_far_add_offset(base, offset));
}
static void write_i32(Win16FarPtr base, uint16_t offset, int32_t value)
{
win16_write_u32(base, offset, (uint32_t)value);
}
static BorlandReal48 read_real48(Win16FarPtr base, uint16_t offset)
{
BorlandReal48 value;
for (uint16_t index = 0; index < sizeof(value.bytes); index++) {
value.bytes[index] = win16_read_u8(win16_far_add_offset(
base, (uint16_t)(offset + index)));
}
return value;
}
static void write_real48(
Win16FarPtr base, uint16_t offset, BorlandReal48 value)
{
for (uint16_t index = 0; index < sizeof(value.bytes); index++) {
win16_write_u8(
win16_far_add_offset(base, (uint16_t)(offset + index)),
value.bytes[index]);
}
}
static int32_t dgroup_i32(uint16_t offset)
{
return (int32_t)win16_read_u32(win16_dgroup_pointer(offset));
}
static void write_dgroup_i32(uint16_t offset, int32_t value)
{
win16_write_u32(win16_dgroup_pointer(offset), 0, (uint32_t)value);
}
static int32_t add_wrap(int32_t left, int32_t right)
{
return (int32_t)((uint32_t)left + (uint32_t)right);
}
static int32_t subtract_wrap(int32_t left, int32_t right)
{
return (int32_t)((uint32_t)left - (uint32_t)right);
}
static int32_t negate_wrap(int32_t value)
{
return (int32_t)(0u - (uint32_t)value);
}
static int32_t absolute_wrap(int32_t value)
{
return value < 0 ? negate_wrap(value) : value;
}
static BorlandReal48 negate_real48(BorlandReal48 value)
{
if (value.bytes[0] != 0) {
value.bytes[5] ^= 0x80;
}
return value;
}
static BorlandReal48 absolute_real48(BorlandReal48 value)
{
value.bytes[5] &= 0x7f;
return value;
}
static int32_t distance_milli(int32_t dx, int32_t dy)
{
BorlandReal48 real_x = borland_real48_divide(
borland_i32_to_real48(dx), g_real48_thousand);
BorlandReal48 real_y = borland_real48_divide(
borland_i32_to_real48(dy), g_real48_thousand);
return borland_real48_round_to_i32(borland_real48_multiply(
borland_real48_sqrt(borland_real48_add(
borland_real48_square(real_x),
borland_real48_square(real_y))),
g_real48_thousand));
}
static int32_t normal_velocity_unchecked(
int32_t velocity_x,
int32_t velocity_y,
BorlandReal48 normal_x,
BorlandReal48 normal_y,
int32_t normal_length)
{
return borland_real48_round_to_i32(borland_real48_divide(
borland_real48_subtract(
borland_real48_multiply(
borland_i32_to_real48(velocity_x), normal_y),
borland_real48_multiply(
borland_i32_to_real48(velocity_y), normal_x)),
borland_i32_to_real48(normal_length)));
}
static int32_t normal_velocity_checked(
int32_t velocity_x,
int32_t velocity_y,
BorlandReal48 normal_x,
BorlandReal48 normal_y,
int32_t normal_length)
{
if (normal_length <= 0) {
return 0;
}
return normal_velocity_unchecked(
velocity_x, velocity_y, normal_x, normal_y, normal_length);
}
static int32_t speed_milli(int32_t velocity_x, int32_t velocity_y)
{
return distance_milli(velocity_x, velocity_y);
}
static int32_t scale_real48(int32_t value, BorlandReal48 factor)
{
return borland_real48_round_to_i32(borland_real48_multiply(
borland_i32_to_real48(value), factor));
}
static int32_t clamp_motion_speed(
Win16FarPtr window, int32_t *velocity_x, int32_t *velocity_y)
{
int32_t speed = speed_milli(*velocity_x, *velocity_y);
int32_t maximum = read_i32(window, 0x56);
if (speed > maximum) {
BorlandReal48 excess_fraction = borland_real48_divide(
borland_i32_to_real48(subtract_wrap(speed, maximum)),
borland_i32_to_real48(speed));
*velocity_x = subtract_wrap(
*velocity_x, scale_real48(*velocity_x, excess_fraction));
*velocity_y = subtract_wrap(
*velocity_y, scale_real48(*velocity_y, excess_fraction));
speed = maximum;
}
return speed;
}
static bool broadphase_contains_predicted_ball(
const TdkpinCollisionRecord *record, uint16_t caller_bp)
{
return (uint8_t)tdkpin_predicted_ball_in_bounds(
caller_bp,
record->bounds_max_y_milli,
record->bounds_max_x_milli,
record->bounds_min_y_milli,
record->bounds_min_x_milli) != 0;
}
static void load_record(uint16_t id, TdkpinCollisionRecord *record)
{
Win16FarPtr source = record_pointer(id);
uint8_t *bytes = (uint8_t *)record;
for (uint16_t index = 0; index < sizeof(*record); index++) {
bytes[index] = win16_read_u8(win16_far_add_offset(source, index));
}
}
static void make_candidate(
PhysicsCollisionCandidate *candidate,
uint16_t id,
const TdkpinCollisionRecord *record,
BorlandReal48 normal_x,
BorlandReal48 normal_y,
int32_t collision_velocity,
int32_t surface_distance)
{
*candidate = (PhysicsCollisionCandidate){
.record_id = id,
.normal_x = normal_x,
.normal_y = normal_y,
.normal_velocity = collision_velocity,
.response_normal = record->response_normal,
.response_tangent = record->response_tangent,
.response_auxiliary = record->response_auxiliary,
.response_kick = record->response_kick,
.flags = record->flags,
.score = (int32_t)((uint32_t)record->score_low |
((uint32_t)record->score_high << 16)),
.surface_distance = surface_distance,
};
}
static bool detect_circle_collision(
const TdkpinCollisionRecord *record,
uint16_t id,
uint16_t ball,
int32_t previous_x,
int32_t previous_y,
int32_t predicted_x,
int32_t predicted_y,
const BallMotion *motion,
PhysicsCollisionCandidate *candidate)
{
if (id == (uint16_t)(175u - ball)) {
return false;
}
int32_t radius = borland_real48_round_to_i32(record->radius_milli);
int32_t surface_distance = subtract_wrap(
absolute_wrap(distance_milli(
subtract_wrap(record->point1_x_milli, previous_x),
subtract_wrap(record->point1_y_milli, previous_y))),
radius);
if ((record->subtype == 1 && surface_distance > motion->speed) ||
(record->subtype == 2 &&
negate_wrap(surface_distance) > motion->speed) ||
(record->subtype != 1 && record->subtype != 2)) {
return false;
}
int32_t middle_x = borland_divide_i32(
add_wrap(previous_x, predicted_x), 2).quotient;
int32_t middle_y = borland_divide_i32(
add_wrap(previous_y, predicted_y), 2).quotient;
BorlandReal48 normal_x = borland_i32_to_real48(
subtract_wrap(record->point1_y_milli, middle_y));
BorlandReal48 normal_y = borland_i32_to_real48(
subtract_wrap(middle_x, record->point1_x_milli));
if (record->subtype == 2) {
normal_x = negate_real48(normal_x);
normal_y = negate_real48(normal_y);
}
int32_t length = distance_milli(
borland_real48_round_to_i32(normal_x),
borland_real48_round_to_i32(normal_y));
int32_t collision_velocity = normal_velocity_checked(
motion->velocity_x,
motion->velocity_y,
normal_x,
normal_y,
length);
if (collision_velocity >= 0 ||
absolute_wrap(surface_distance) > absolute_wrap(collision_velocity)) {
return false;
}
surface_distance = add_wrap(surface_distance, 3000);
make_candidate(
candidate,
id,
record,
normal_x,
normal_y,
collision_velocity,
surface_distance);
return true;
}
static int32_t cross_at_endpoint(
int32_t point_x,
int32_t point_y,
int32_t current_x,
int32_t current_y,
int32_t predicted_x,
int32_t predicted_y)
{
return subtract_wrap(
borland_multiply_i32(
subtract_wrap(predicted_x, point_x),
subtract_wrap(point_y, current_y)),
borland_multiply_i32(
subtract_wrap(predicted_y, point_y),
subtract_wrap(point_x, current_x)));
}
static bool detect_segment_collision(
const TdkpinCollisionRecord *record,
uint16_t id,
int32_t previous_x,
int32_t previous_y,
int32_t predicted_x,
int32_t predicted_y,
const BallMotion *motion,
PhysicsCollisionCandidate *candidate)
{
int32_t segment_x = subtract_wrap(
record->point2_x_milli, record->point1_x_milli);
int32_t segment_y = subtract_wrap(
record->point2_y_milli, record->point1_y_milli);
BorlandReal48 normal_x = borland_i32_to_real48(segment_x);
BorlandReal48 normal_y = borland_i32_to_real48(segment_y);
int32_t length = distance_milli(segment_x, segment_y);
BorlandReal48 length_units = borland_real48_divide(
borland_i32_to_real48(length), g_real48_thousand);
BorlandReal48 segment_x_units = borland_real48_divide(
normal_x, g_real48_thousand);
BorlandReal48 segment_y_units = borland_real48_divide(
normal_y, g_real48_thousand);
int32_t distance = borland_real48_round_to_i32(absolute_real48(
borland_real48_divide(
borland_real48_subtract(
borland_real48_multiply(
borland_i32_to_real48(subtract_wrap(
previous_x, record->point1_x_milli)),
segment_y_units),
borland_real48_multiply(
borland_i32_to_real48(subtract_wrap(
previous_y, record->point1_y_milli)),
segment_x_units)),
length_units)));
int32_t collision_velocity = borland_real48_round_to_i32(
borland_real48_divide(
borland_real48_subtract(
borland_real48_multiply(
borland_i32_to_real48(motion->velocity_x),
segment_y_units),
borland_real48_multiply(
borland_i32_to_real48(motion->velocity_y),
segment_x_units)),
length_units));
if (distance > motion->speed || collision_velocity > 10 ||
subtract_wrap(distance, 10) > absolute_wrap(collision_velocity) ||
cross_at_endpoint(
record->point1_x_milli,
record->point1_y_milli,
previous_x,
previous_y,
predicted_x,
predicted_y) < -10 ||
cross_at_endpoint(
record->point2_x_milli,
record->point2_y_milli,
previous_x,
previous_y,
predicted_x,
predicted_y) > 10) {
return false;
}
make_candidate(
candidate,
id,
record,
normal_x,
normal_y,
collision_velocity,
distance);
return true;
}
static void add_velocity_toward(
Win16FarPtr window,
int32_t target_x,
int32_t target_y,
int32_t ball_x,
int32_t ball_y,
BallMotion *motion)
{
int32_t acceleration = borland_multiply_i32(read_i32(window, 0x5a), 10);
motion->velocity_x = scale_real48(
motion->velocity_x, g_real48_point_nine);
motion->velocity_y = scale_real48(
motion->velocity_y, g_real48_point_nine);
motion->velocity_x = target_x > ball_x
? add_wrap(motion->velocity_x, acceleration)
: subtract_wrap(motion->velocity_x, acceleration);
motion->velocity_y = target_y > ball_y
? add_wrap(motion->velocity_y, acceleration)
: subtract_wrap(motion->velocity_y, acceleration);
}
static bool update_target_set_award(
uint16_t caller_bp, Win16FarPtr window)
{
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
int32_t award = 5000;
for (uint16_t id = 129; id <= 133; id++) {
if (win16_read_u16(
win16_far_add_offset(record_pointer(id), 0x43)) != 0) {
award = borland_multiply_i32(award, 2);
}
}
Win16FarPtr accumulated = win16_dgroup_pointer(
(uint16_t)(0x098e + player * 4u));
win16_write_u32(
accumulated,
0,
win16_read_u32(accumulated) + (uint32_t)award);
if (award == 160000) {
uint8_t multiplier = win16_read_u8(win16_dgroup_pointer(
(uint16_t)(0x09a1 + player)));
tdkpin_add_score_from_caller_frame(
caller_bp,
borland_multiply_i32(
(int32_t)win16_read_u32(accumulated), multiplier));
win16_write_u32(accumulated, 0, 0);
win16_write_u8(
win16_dgroup_pointer((uint16_t)(0x09a1 + player)),
(uint8_t)(multiplier + 1u));
Win16FarPtr markers = win16_dgroup_pointer(
(uint16_t)(0x081f + player * 2u));
win16_write_u16(markers, 0, (uint16_t)(win16_read_u16(markers) + 1u));
tdkpin_refresh_player_progress_markers(caller_bp);
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
HDC16 dc = GetDC16(handle);
(void)ReleaseDC16(handle, dc);
if (win16_read_u8(win16_dgroup_pointer(0x07c7)) == 0 &&
win16_read_u8(win16_dgroup_pointer(0x07c8)) == 0 &&
win16_read_u8(win16_dgroup_pointer(0x0874)) == 0) {
write_dgroup_i32(0x0851, 0);
win16_write_u8(win16_dgroup_pointer(0x07c8), 1);
win16_write_u8(win16_far_add_offset(window, 0x0bdd), 0);
}
win16_write_u8(win16_dgroup_pointer(0x0874), 0);
return true;
}
return false;
}
static void start_claw_capture(Win16FarPtr window, BallMotion *motion)
{
win16_write_u8(win16_dgroup_pointer(0x07ca), 1);
static const uint16_t target_frames[4] = {1, 6, 7, 18};
win16_write_u16(
win16_dgroup_pointer(0x086f),
0,
target_frames[borland_random_below(4)]);
win16_write_u8(win16_dgroup_pointer(0x0873), 1);
win16_write_u8(win16_far_add_offset(window, 0x0bdf), 1);
motion->spin = (BorlandReal48){{0,0,0,0,0,0}};
for (uint16_t id = 12; id <= 20; id++) {
win16_write_u8(
win16_far_add_offset(record_pointer(id), 0x34), 0);
}
}
static bool handle_capture_record(
TdkpinCollisionRecord *record,
uint16_t id,
uint16_t ball,
uint16_t ball_count,
uint16_t caller_bp,
Win16FarPtr window,
int32_t previous_x,
int32_t previous_y,
int32_t ball_x,
int32_t ball_y,
BallMotion *motion,
PhysicsCollisionCandidate *candidate)
{
int32_t radius = borland_real48_round_to_i32(record->radius_milli);
int32_t surface_distance = subtract_wrap(
absolute_wrap(distance_milli(
subtract_wrap(record->point1_x_milli, ball_x),
subtract_wrap(
subtract_wrap(record->point1_y_milli, ball_y), 2000))),
radius);
Win16FarPtr live = record_pointer(id);
uint16_t contact = win16_read_u16(win16_far_add_offset(live, 0x43));
bool contact_allowed = contact == 0 ||
(contact != 99 && ball_count == 1) || contact == ball;
if (surface_distance < -11000 && contact_allowed &&
(id != 148 || win16_read_u8(win16_dgroup_pointer(0x0875)) == 0)) {
int32_t capture_age = read_i32(window, 0x0bca);
if (capture_age < 300) {
bool stationary =
absolute_wrap(subtract_wrap(
record->point1_x_milli, previous_x)) < 500 &&
absolute_wrap(subtract_wrap(
record->point1_y_milli, previous_y)) < 500 &&
absolute_wrap(motion->velocity_x) < 500 &&
absolute_wrap(motion->velocity_y) < 500;
if (stationary) {
motion->velocity_x = 0;
motion->velocity_y = 0;
if (capture_age == 0) {
win16_write_u16(live, 0x43, ball);
tdkpin_play_sound_if_not_tilted(15);
}
write_i32(window, 0x0bca, id == 89 ? 300
: add_wrap(capture_age, 5));
} else {
add_velocity_toward(
window,
record->point1_x_milli,
record->point1_y_milli,
ball_x,
ball_y,
motion);
}
return false;
}
if (id == 89 && win16_read_u8(win16_dgroup_pointer(0x0874)) == 0) {
start_claw_capture(window, motion);
return false;
}
if (id >= 129 && id <= 133) {
bool complete = update_target_set_award(caller_bp, window);
if (!complete) {
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
win16_write_u8(
win16_far_add_offset(
window,
(uint16_t)(player * 0x14u + (id - 129u) +
0x0476u)),
1);
tdkpin_restore_record_or_item_a(
caller_bp, (uint16_t)(id - 129u + 1015u));
}
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
int32_t accumulated = dgroup_i32((uint16_t)(0x098e + player * 4u));
uint8_t multiplier = win16_read_u8(win16_dgroup_pointer(
(uint16_t)(0x09a1 + player)));
tdkpin_draw_score_from_caller_frame(
caller_bp,
(uint16_t)borland_multiply_i32(accumulated, multiplier),
(uint16_t)((uint32_t)borland_multiply_i32(
accumulated, multiplier) >> 16));
} else if (id == 148) {
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
win16_write_u8(
win16_far_add_offset(
window, (uint16_t)(player * 0x14u + 0x047b)), 1);
tdkpin_restore_record_or_item_a(caller_bp, 1020);
}
write_i32(window, 0x0bca, 0);
if (ball_count == 1) {
win16_write_u16(live, 0x43, id == 148 ? 2 : 99);
if (win16_read_u8(win16_dgroup_pointer(0x07c6)) == 0) {
win16_write_u8(win16_far_add_offset(window, 0x0be2), 1);
} else {
tdkpin_finish_ball_or_advance_player(caller_bp);
}
} else {
win16_write_u8(win16_far_add_offset(window, 0x0be1), 1);
win16_write_u8(
win16_far_add_offset(
record_pointer((uint16_t)(ball + 173u)), 0x34),
0);
win16_write_u16(live, 0x43, 2);
}
return false;
}
if (surface_distance > -11000 && contact == ball) {
win16_write_u16(live, 0x43, 0);
write_i32(window, 0x0bca, 0);
return false;
}
if (surface_distance > motion->speed || surface_distance < -11000 ||
contact == 0) {
return false;
}
BorlandReal48 normal_x = borland_i32_to_real48(
subtract_wrap(record->point1_y_milli, previous_y));
BorlandReal48 normal_y = borland_i32_to_real48(
subtract_wrap(previous_x, record->point1_x_milli));
int32_t length = distance_milli(
borland_real48_round_to_i32(normal_x),
borland_real48_round_to_i32(normal_y));
int32_t collision_velocity = normal_velocity_checked(
motion->velocity_x,
motion->velocity_y,
normal_x,
normal_y,
length);
if (collision_velocity >= 0 ||
absolute_wrap(collision_velocity) < absolute_wrap(surface_distance)) {
return false;
}
make_candidate(
candidate,
id,
record,
normal_x,
normal_y,
collision_velocity,
surface_distance);
return true;
}
static void handle_trigger_record(
TdkpinCollisionRecord *record,
uint16_t id,
uint16_t ball,
uint16_t caller_bp,
Win16FarPtr window,
int32_t ball_x,
int32_t ball_y,
BallMotion *motion)
{
if (win16_read_u8(win16_dgroup_pointer(0x07c6)) != 0) {
return;
}
win16_write_u8(win16_dgroup_pointer(0x0875), 0);
int32_t radius = borland_real48_round_to_i32(record->radius_milli);
if (radius <= 0) {
if ((uint8_t)tdkpin_receiver_point_in_bounds(
caller_bp,
record->point2_y_milli,
record->point2_x_milli,
record->point1_y_milli,
record->point1_x_milli) != 0 &&
(id == 153 || id == 154 || id == 6)) {
motion->velocity_x = scale_real48(
motion->velocity_x, g_real48_point_nine);
BorlandReal48 random_component = borland_real48_multiply(
borland_random_real48_registers(), g_real48_point_three);
motion->velocity_y = negate_wrap(scale_real48(
read_i32(window, 0x56),
borland_real48_subtract(g_real48_one, random_component)));
write_dgroup_i32(
0x07db, add_wrap(ball_x, motion->velocity_x));
write_dgroup_i32(
0x07df, add_wrap(ball_y, motion->velocity_y));
if ((uint8_t)tdkpin_predicted_ball_in_bounds(
0,
record->point2_y_milli,
record->point2_x_milli,
record->point1_y_milli,
record->point1_x_milli) == 0) {
win16_write_u8(
win16_far_add_offset(record_pointer(id), 0x34), 0);
}
tdkpin_restore_record_b(caller_bp, id);
}
return;
}
int32_t current_distance = absolute_wrap(distance_milli(
subtract_wrap(record->point1_x_milli, ball_x),
subtract_wrap(record->point1_y_milli, ball_y)));
int32_t predicted_distance = absolute_wrap(distance_milli(
subtract_wrap(record->point1_x_milli, dgroup_i32(0x07db)),
subtract_wrap(record->point1_y_milli, dgroup_i32(0x07df))));
Win16FarPtr live = record_pointer(id);
if (predicted_distance <= radius) {
uint16_t flags = win16_read_u16(win16_far_add_offset(live, 0x41));
if ((flags & 0x4000u) == 0) {
flags = (uint16_t)(flags | 0x4000u);
win16_write_u16(live, 0x41, flags);
tdkpin_play_sound_if_not_tilted(4);
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
uint8_t score_factor = win16_read_u8(win16_dgroup_pointer(
(uint16_t)(0x081a + player)));
int32_t score = (int32_t)((uint32_t)record->score_low |
((uint32_t)record->score_high << 16));
tdkpin_add_score_from_caller_frame(
caller_bp, borland_multiply_i32(score_factor, score));
if (id == 149) {
win16_write_u8(win16_far_add_offset(live, 0x34), 0);
win16_write_u8(
win16_far_add_offset(
window, (uint16_t)(player * 0x00afu + id + 0x61u)),
0);
tdkpin_restore_record_b(caller_bp, id);
tdkpin_apply_player_sprite_effect(caller_bp, ball);
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
HDC16 dc = GetDC16(handle);
win16_write_u16(
win16_dgroup_pointer((uint16_t)(0x09a4 + player * 2u)),
0,
0);
tdkpin_composite_player_sprite(0, dc);
tdkpin_present_background_region(123, 42, 79, 55, dc);
(void)ReleaseDC16(handle, dc);
}
tdkpin_handle_collision_events(caller_bp, flags, id);
static const BorlandReal48 random_span =
{{0x7d,0x71,0x3d,0x0a,0xd7,0x23}};
static const BorlandReal48 random_offset =
{{0x81,0x71,0x3d,0x0a,0xd7,0x03}};
motion->velocity_x = scale_real48(
motion->velocity_x,
borland_real48_subtract(
random_offset,
borland_real48_multiply(
borland_random_real48_registers(), random_span)));
motion->velocity_y = scale_real48(
motion->velocity_y,
borland_real48_subtract(
random_offset,
borland_real48_multiply(
borland_random_real48_registers(), random_span)));
}
} else {
win16_write_u16(
live,
0x41,
(uint16_t)(win16_read_u16(win16_far_add_offset(live, 0x41)) &
0xbfffu));
}
(void)current_distance;
}
static bool scan_collision_records(
uint16_t caller_bp,
Win16FarPtr window,
uint16_t ball,
uint16_t ball_count,
int32_t previous_x,
int32_t previous_y,
int32_t predicted_x,
int32_t predicted_y,
uint16_t layer,
BallMotion *motion,
PhysicsCollisionCandidate candidates[MAX_RECORDS + 1],
uint16_t *candidate_count)
{
int32_t closest = 999999;
(void)previous_x;
(void)previous_y;
(void)predicted_x;
(void)predicted_y;
for (uint16_t id = 1; id <= MAX_RECORDS; id++) {
TdkpinCollisionRecord record;
load_record(id, &record);
if ((record.layer_mask & layer) == 0 || record.active != 1 ||
!broadphase_contains_predicted_ball(&record, caller_bp)) {
continue;
}
PhysicsCollisionCandidate candidate;
bool detected = false;
int32_t record_previous_x = read_i32(window, 0x0bba);
int32_t record_previous_y = read_i32(window, 0x0bbe);
int32_t record_predicted_x = dgroup_i32(0x07db);
int32_t record_predicted_y = dgroup_i32(0x07df);
if (record.type == 1) {
detected = detect_circle_collision(
&record,
id,
ball,
record_previous_x,
record_previous_y,
record_predicted_x,
record_predicted_y,
motion,
&candidate);
} else if (record.type == 2) {
detected = detect_segment_collision(
&record,
id,
record_previous_x,
record_previous_y,
record_predicted_x,
record_predicted_y,
motion,
&candidate);
} else if (record.type == 3) {
detected = handle_capture_record(
&record,
id,
ball,
ball_count,
caller_bp,
window,
record_previous_x,
record_previous_y,
dgroup_i32(0x07d3),
dgroup_i32(0x07d7),
motion,
&candidate);
} else if (record.type == 4) {
handle_trigger_record(
&record,
id,
ball,
caller_bp,
window,
dgroup_i32(0x07d3),
dgroup_i32(0x07d7),
motion);
}
if (detected && candidate.surface_distance <= closest) {
closest = candidate.surface_distance;
(*candidate_count)++;
candidates[*candidate_count] = candidate;
write_i32(window, 0x0bca, 0);
}
}
return *candidate_count != 0;
}
static int32_t candidate_normal_length(
const PhysicsCollisionCandidate *candidate)
{
return distance_milli(
borland_real48_round_to_i32(candidate->normal_x),
borland_real48_round_to_i32(candidate->normal_y));
}
static bool transfer_ball_collision_impulse(
const PhysicsCollisionCandidate *candidate,
int32_t normal_length)
{
if (candidate->record_id != 174 && candidate->record_id != 175) {
return false;
}
uint16_t slot = (uint16_t)(candidate->record_id - 174u);
if (dgroup_i32((uint16_t)(0x0803 + slot * 4u)) > 0) {
return false;
}
int32_t delta_x = borland_real48_round_to_i32(borland_real48_divide(
borland_real48_multiply(
borland_i32_to_real48(candidate->normal_velocity),
candidate->normal_y),
borland_i32_to_real48(normal_length)));
int32_t delta_y = borland_real48_round_to_i32(borland_real48_divide(
borland_real48_multiply(
borland_i32_to_real48(candidate->normal_velocity),
candidate->normal_x),
borland_i32_to_real48(normal_length)));
write_dgroup_i32(
(uint16_t)(0x07f3 + slot * 4u),
add_wrap(dgroup_i32((uint16_t)(0x07f3 + slot * 4u)), delta_x));
write_dgroup_i32(
(uint16_t)(0x07fb + slot * 4u),
subtract_wrap(dgroup_i32((uint16_t)(0x07fb + slot * 4u)), delta_y));
return true;
}
static void apply_collision_response(
uint16_t caller_bp,
Win16FarPtr window,
uint16_t ball,
uint16_t ball_count,
PhysicsCollisionCandidate *candidate,
BallMotion *motion)
{
if (candidate->flags != 0) {
tdkpin_handle_collision_events(
caller_bp, candidate->flags, candidate->record_id);
}
if (candidate->score != 0 &&
borland_real48_compare(
candidate->response_kick,
(BorlandReal48){{0,0,0,0,0,0}}) == 0) {
tdkpin_add_score_from_caller_frame(caller_bp, candidate->score);
}
int32_t length = candidate_normal_length(candidate);
int32_t projection = normal_velocity_unchecked(
motion->velocity_x,
motion->velocity_y,
candidate->normal_x,
candidate->normal_y,
length);
BorlandReal48 spin_delta = borland_real48_multiply(
borland_real48_multiply(
borland_i32_to_real48(projection), g_real48_point_zero_two),
candidate->response_tangent);
spin_delta = borland_real48_multiply(
borland_i32_to_real48(absolute_wrap(candidate->normal_velocity)),
spin_delta);
if (projection < 0) {
spin_delta = negate_real48(spin_delta);
}
BorlandReal48 discarded = borland_real48_divide(
borland_real48_sqrt(borland_real48_multiply(
absolute_real48(spin_delta), g_real48_twenty)),
borland_i32_to_real48(motion->speed));
(void)discarded;
discarded = (BorlandReal48){{0,0,0,0,0,0}};
projection = borland_real48_round_to_i32(borland_real48_subtract(
borland_real48_subtract(
borland_real48_multiply(motion->spin, g_real48_point_four),
borland_real48_divide(
borland_real48_multiply(
borland_i32_to_real48(absolute_wrap(projection)),
discarded),
borland_i32_to_real48(motion->speed))),
candidate->response_tangent));
motion->spin = borland_real48_add(
borland_real48_multiply(motion->spin, g_real48_point_six),
spin_delta);
if (transfer_ball_collision_impulse(candidate, length)) {
candidate->normal_velocity = subtract_wrap(
candidate->normal_velocity, 1000);
}
int32_t impulse = borland_real48_round_to_i32(borland_real48_multiply(
borland_i32_to_real48(candidate->normal_velocity),
borland_real48_add(g_real48_one, candidate->response_normal)));
if (win16_read_u8(win16_dgroup_pointer(0x07c6)) == 0 &&
borland_real48_compare(
candidate->response_auxiliary,
(BorlandReal48){{0,0,0,0,0,0}}) < 0) {
int32_t threshold = scale_real48(
read_i32(window, 0x56), candidate->response_auxiliary);
if (threshold > candidate->normal_velocity) {
if (candidate->score != 0) {
tdkpin_add_score_from_caller_frame(caller_bp, candidate->score);
} else {
tdkpin_play_sound_if_not_tilted(19);
}
impulse = subtract_wrap(
impulse,
scale_real48(read_i32(window, 0x56), candidate->response_kick));
}
}
int32_t delta_velocity_x = borland_real48_round_to_i32(
borland_real48_divide(
borland_real48_subtract(
borland_real48_multiply(
borland_i32_to_real48(projection), candidate->normal_x),
borland_real48_multiply(
borland_i32_to_real48(impulse), candidate->normal_y)),
borland_i32_to_real48(length)));
int32_t delta_velocity_y = borland_real48_round_to_i32(
borland_real48_divide(
borland_real48_add(
borland_real48_multiply(
borland_i32_to_real48(impulse), candidate->normal_x),
borland_real48_multiply(
borland_i32_to_real48(projection), candidate->normal_y)),
borland_i32_to_real48(length)));
motion->velocity_x = add_wrap(motion->velocity_x, delta_velocity_x);
motion->velocity_y = add_wrap(motion->velocity_y, delta_velocity_y);
if (candidate->record_id == 25) {
if (motion->speed < 1000) {
win16_write_u8(win16_far_add_offset(window, 0x0bdd), 0);
motion->velocity_y = 0;
}
motion->velocity_x = 0;
} else if (candidate->record_id == 2) {
if (ball_count < 2) {
tdkpin_finish_ball_or_advance_player(caller_bp);
} else {
win16_write_u8(
win16_far_add_offset(
record_pointer((uint16_t)(ball + 173u)), 0x34),
0);
win16_write_u8(win16_dgroup_pointer(0x0874), 0);
}
}
}
static bool predicted_outside_board(int32_t x, int32_t y)
{
return x <= 0 || x > 340000 || y <= 0 || y > 460000;
}
static void integrate_one_ball(
uint16_t caller_bp,
Win16FarPtr window,
uint16_t ball,
uint16_t ball_count,
uint16_t substeps)
{
int32_t previous_x = dgroup_i32(0x07d3);
int32_t previous_y = dgroup_i32(0x07d7);
write_i32(window, 0x0bc2, previous_x);
write_i32(window, 0x0bc6, previous_y);
if (substeps == 0) {
return;
}
for (uint16_t step = 1; step <= substeps; step++) {
BallMotion motion = {
.velocity_x = read_i32(window, 0x0baa),
.velocity_y = add_wrap(
read_i32(window, 0x0bae), read_i32(window, 0x5a)),
.spin = read_real48(window, 0x0bce),
.speed = 0,
};
motion.speed = clamp_motion_speed(
window, &motion.velocity_x, &motion.velocity_y);
write_i32(window, 0x0baa, motion.velocity_x);
write_i32(window, 0x0bae, motion.velocity_y);
previous_x = dgroup_i32(0x07d3);
previous_y = dgroup_i32(0x07d7);
write_i32(window, 0x0bba, previous_x);
write_i32(window, 0x0bbe, previous_y);
int32_t predicted_x = add_wrap(previous_x, motion.velocity_x);
int32_t predicted_y = add_wrap(previous_y, motion.velocity_y);
write_dgroup_i32(0x07db, predicted_x);
write_dgroup_i32(0x07df, predicted_y);
if (predicted_outside_board(predicted_x, predicted_y)) {
if (ball_count < 2) {
tdkpin_finish_ball_or_advance_player(caller_bp);
} else {
win16_write_u8(
win16_far_add_offset(
record_pointer((uint16_t)(ball + 173u)), 0x34),
0);
win16_write_u8(win16_dgroup_pointer(0x0874), 0);
}
}
uint16_t layer = dgroup_i32(0x07d7) < 250000 ? 1 : 2;
PhysicsCollisionCandidate candidates[MAX_RECORDS + 1];
uint16_t candidate_count = 0;
bool collided = scan_collision_records(
caller_bp,
window,
ball,
ball_count,
previous_x,
previous_y,
predicted_x,
predicted_y,
layer,
&motion,
candidates,
&candidate_count);
if (collided) {
/* Raw 1000:c79c stores each later, nearer candidate at the next
* 0x34-byte stack slot but resolves the fixed first slot at
* SS:...d8a2. Record order therefore wins over closest distance. */
apply_collision_response(
caller_bp,
window,
ball,
ball_count,
&candidates[1],
&motion);
}
if (win16_read_u8(win16_far_add_offset(window, 0x0bdd)) != 0) {
write_real48(window, 0x0bce, motion.spin);
write_i32(window, 0x0baa, motion.velocity_x);
write_i32(window, 0x0bae, motion.velocity_y);
motion.speed = clamp_motion_speed(
window, &motion.velocity_x, &motion.velocity_y);
write_i32(window, 0x0baa, motion.velocity_x);
write_i32(window, 0x0bae, motion.velocity_y);
write_dgroup_i32(
0x07d3, add_wrap(dgroup_i32(0x07d3), motion.velocity_x));
write_dgroup_i32(
0x07d7, add_wrap(dgroup_i32(0x07d7), motion.velocity_y));
tdkpin_update_dynamic_ball_record(caller_bp, ball);
}
if (collided ||
win16_read_u8(win16_far_add_offset(window, 0x0be2)) == 1 ||
win16_read_u8(win16_far_add_offset(window, 0x0be1)) == 1) {
break;
}
}
}
void tdkpin_simulate_ball_and_dispatch_collision(uint16_t caller_bp)
{
Win16FarPtr window = caller_receiver(caller_bp);
win16_write_u32(
window,
0x4d,
win16_read_u32(win16_far_add_offset(window, 0x4d)) + 1u);
uint16_t substeps = win16_read_u16(win16_dgroup_pointer(0x0871));
uint16_t ball_count = win16_read_u8(win16_far_add_offset(window, 0x61));
for (uint16_t ball = 1; ball <= ball_count; ball++) {
if (win16_read_u8(win16_far_add_offset(window, 0x61)) > 1) {
tdkpin_load_ball_slot(caller_bp, (uint16_t)(ball - 1u));
}
integrate_one_ball(caller_bp, window, ball, ball_count, substeps);
if (win16_read_u8(win16_far_add_offset(window, 0x0bdd)) != 0 &&
win16_read_u8(win16_far_add_offset(window, 0x0be2)) == 0) {
tdkpin_render_ball_swept_region(caller_bp);
}
if (win16_read_u8(win16_far_add_offset(window, 0x61)) > 1) {
tdkpin_save_ball_slot(caller_bp, (uint16_t)(ball - 1u));
}
}
}