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
1141 lines
40 KiB
C
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));
|
|
}
|
|
}
|
|
}
|