Replace the partial mechanics transcriptions with a separate, readable C11 reconstruction of the complete Win16 image while preserving the original raw Ghidra export as immutable evidence. Cover all ordinary and overlapping entry points, Borland runtime behavior, Win16 imports, segmented data, callbacks, resources, indirect control flow, physics, rendering, persistence, and startup/shutdown lifecycles. Add deterministic extraction and audit tooling plus address-linked ledgers for functions, imports, DGROUP ranges and objects, relocations, resources, and callbacks. The final gate records zero raw, partial, restored, unknown, blocked, or unclassified required units. Keep the semantic-fidelity boundary explicit: the portable C is not claimed to reproduce a byte-identical Borland NE build. Add strict focused harnesses for every reconstructed C unit, exact resource round-trip checks, and a 16-bit Borland Real48 reference probe. No Rust source or Cargo metadata is changed in this phase. Test Plan: - `bash original/tools/test_reconstructed_c.sh` -- passed - `bash original/tools/probe_real48_reference.sh` -- passed bit-for-bit - `python3 original/tools/audit_reconstruction.py --require-complete` -- passed - `git diff --cached --check` -- passed - `git diff HEAD -- '*.rs' Cargo.toml Cargo.lock` -- empty
1138 lines
40 KiB
C
1138 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_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_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) {
|
|
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));
|
|
}
|
|
}
|
|
}
|