Raw 1000:061c-063b divides the idle tick by 144, copies the remainder from CX:BX into AX:DX, and only then divides by 8. Readable C and Rust instead used an unbounded quotient equivalent to tick/1152, making DAT801-809 advance far too slowly and suggesting a nonexistent negative long-idle index. Use `(tick % 144) / 8` to repeat item states 0,1..9,8..1 every 144 callbacks. Remove the disproven long-idle status crops and restore the ten-minute simulator ceiling. Extend the C and Rust phase tests across the forward, reverse, and wrap boundaries. Test Plan: - raw instruction review at `1000:061c-063b` -- remainder transfer confirmed - `cargo test --workspace --all-targets --all-features` -- 125 passed - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed - `bash original/tools/test_reconstructed_c.sh` -- passed - `python3 original/tools/audit_reconstruction.py --require-complete` -- passed with zero incomplete or unclassified units - `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed - `git diff --cached --check` -- passed
661 lines
22 KiB
C
661 lines
22 KiB
C
/* Multimedia timer callback exported at 1000:eab1. */
|
|
|
|
#include "tdkpin_timer_tick.h"
|
|
|
|
#include "tdkpin_arithmetic.h"
|
|
#include "tdkpin_collision_records.h"
|
|
#include "tdkpin_flippers.h"
|
|
#include "tdkpin_gameplay_helpers.h"
|
|
#include "tdkpin_mechanism_render.h"
|
|
#include "tdkpin_message_pump.h"
|
|
#include "tdkpin_panel_animation.h"
|
|
#include "tdkpin_physics.h"
|
|
#include "tdkpin_player_state.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,
|
|
RECORD_ACTIVE = 0x34,
|
|
};
|
|
|
|
typedef struct {
|
|
uint16_t claw_position;
|
|
int32_t ball_x;
|
|
int32_t ball_y;
|
|
BorlandReal48 velocity_x_factor;
|
|
BorlandReal48 velocity_y_factor;
|
|
bool direct_velocity;
|
|
} ClawRelease;
|
|
|
|
static const ClawRelease g_claw_releases[] = {
|
|
{1, 258000, 78000,
|
|
{{0x80,0x9a,0x99,0x99,0x99,0x99}}, {{0,0,0,0,0,0}}, false},
|
|
{2, 259000, 81000,
|
|
{{0x7f,0x66,0x66,0x66,0x66,0xe6}},
|
|
{{0x7c,0xcd,0xcc,0xcc,0xcc,0x4c}}, false},
|
|
{3, 258000, 78000,
|
|
{{0x80,0x33,0x33,0x33,0x33,0xb3}},
|
|
{{0x7f,0x9a,0x99,0x99,0x99,0x19}}, false},
|
|
{4, 261000, 86000,
|
|
{{0x7f,0x9a,0x99,0x99,0x99,0x99}},
|
|
{{0x7e,0xcd,0xcc,0xcc,0xcc,0x4c}}, false},
|
|
{5, 266000, 90000,
|
|
{{0x7f,0x71,0x3d,0x0a,0xd7,0xa3}},
|
|
{{0x7e,0x7b,0x14,0xae,0x47,0x61}}, false},
|
|
{6, 270000, 94000,
|
|
{{0x7f,0xcd,0xcc,0xcc,0xcc,0xcc}},
|
|
{{0x80,0x9a,0x99,0x99,0x99,0x19}}, false},
|
|
{7, 275000, 97000,
|
|
{{0x7f,0x85,0xeb,0x51,0xb8,0x9e}},
|
|
{{0x80,0x33,0x33,0x33,0x33,0x33}}, false},
|
|
{8, 278000, 98000,
|
|
{{0x7e,0xcd,0xcc,0xcc,0xcc,0xcc}},
|
|
{{0x80,0xcd,0xcc,0xcc,0xcc,0x4c}}, false},
|
|
{9, 282000, 101000,
|
|
{{0x7d,0xcd,0xcc,0xcc,0xcc,0xcc}},
|
|
{{0x80,0x66,0x66,0x66,0x66,0x66}}, false},
|
|
{18, 325000, 92000,
|
|
{{0,0,0,0,0,0}}, {{0,0,0,0,0,0}}, true},
|
|
};
|
|
|
|
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 uint32_t tick_count(void)
|
|
{
|
|
return win16_read_u32(win16_dgroup_pointer(0x0876));
|
|
}
|
|
|
|
static int32_t quotient_i32(int32_t dividend, int32_t divisor)
|
|
{
|
|
return borland_divide_i32(dividend, divisor).quotient;
|
|
}
|
|
|
|
static int32_t remainder_i32(int32_t dividend, int32_t divisor)
|
|
{
|
|
return borland_divide_i32(dividend, divisor).remainder;
|
|
}
|
|
|
|
static Win16FarPtr collision_record(uint16_t id)
|
|
{
|
|
return win16_dgroup_pointer((uint16_t)(RECORDS_BASE + id * RECORD_BYTES));
|
|
}
|
|
|
|
static void write_receiver_i32(
|
|
Win16FarPtr window, uint16_t offset, int32_t value)
|
|
{
|
|
win16_write_u32(window, offset, (uint32_t)value);
|
|
}
|
|
|
|
static void refresh_region(
|
|
Win16FarPtr window,
|
|
bool background_b,
|
|
INT16 height,
|
|
INT16 width,
|
|
INT16 y,
|
|
INT16 x)
|
|
{
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
if (background_b) {
|
|
tdkpin_restore_background_region_b(height, width, y, x, dc);
|
|
} else {
|
|
tdkpin_restore_background_region_a(height, width, y, x, dc);
|
|
}
|
|
tdkpin_present_background_region(height, width, y, x, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void draw_claw(
|
|
Win16FarPtr window, uint8_t alternate, uint16_t frame)
|
|
{
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
tdkpin_draw_claw_frame(alternate, frame, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static const ClawRelease *find_claw_release(uint16_t claw_position)
|
|
{
|
|
for (size_t index = 0;
|
|
index < sizeof(g_claw_releases) / sizeof(g_claw_releases[0]);
|
|
index++) {
|
|
if (g_claw_releases[index].claw_position == claw_position) {
|
|
return &g_claw_releases[index];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void release_ball_from_claw(
|
|
uint16_t frame_bp, Win16FarPtr window, uint16_t claw_position)
|
|
{
|
|
draw_claw(window, 0, claw_position);
|
|
win16_write_u16(win16_dgroup_pointer(0x086f), 0, 10);
|
|
win16_write_u8(win16_dgroup_pointer(0x0873), 0);
|
|
win16_write_u16(win16_dgroup_pointer(0x2679), 0, 0);
|
|
write_receiver_i32(window, 0x0bca, 0);
|
|
for (uint16_t index = 0; index < 6; index++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(window, (uint16_t)(0x0bce + index)), 0);
|
|
}
|
|
|
|
const ClawRelease *release = find_claw_release(claw_position);
|
|
if (release != NULL) {
|
|
write_dgroup_i32(0x07d3, release->ball_x);
|
|
write_dgroup_i32(0x07d7, release->ball_y);
|
|
if (release->direct_velocity) {
|
|
write_receiver_i32(window, 0x0baa, 0);
|
|
write_receiver_i32(window, 0x0bae, 1000);
|
|
} else {
|
|
BorlandReal48 scalar = borland_i32_to_real48(
|
|
(int32_t)win16_read_u32(win16_far_add_offset(window, 0x56)));
|
|
write_receiver_i32(
|
|
window,
|
|
0x0baa,
|
|
borland_real48_round_to_i32(borland_real48_multiply(
|
|
scalar, release->velocity_x_factor)));
|
|
write_receiver_i32(
|
|
window,
|
|
0x0bae,
|
|
borland_real48_round_to_i32(borland_real48_multiply(
|
|
scalar, release->velocity_y_factor)));
|
|
}
|
|
}
|
|
tdkpin_save_ball_slot(frame_bp, 1);
|
|
tdkpin_render_ball_from_caller_frame(frame_bp);
|
|
tdkpin_play_sound_if_not_tilted(16);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdf), 0);
|
|
}
|
|
|
|
static void update_claw_state(uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07ca)) == 0 ||
|
|
win16_read_u8(win16_dgroup_pointer(0x0874)) != 0) {
|
|
return;
|
|
}
|
|
uint16_t current = win16_read_u16(win16_dgroup_pointer(0x086d));
|
|
uint16_t target = win16_read_u16(win16_dgroup_pointer(0x086f));
|
|
if (current != target) {
|
|
current = target > current ? (uint16_t)(current + 1u)
|
|
: (uint16_t)(current - 1u);
|
|
win16_write_u16(win16_dgroup_pointer(0x086d), 0, current);
|
|
draw_claw(
|
|
window,
|
|
win16_read_u8(win16_dgroup_pointer(0x0873)),
|
|
current);
|
|
return;
|
|
}
|
|
if (current != 10) {
|
|
release_ball_from_claw(frame_bp, window, current);
|
|
return;
|
|
}
|
|
|
|
draw_claw(window, 0, 0);
|
|
if (win16_read_u8(win16_dgroup_pointer(0x0873)) == 0) {
|
|
win16_write_u8(win16_dgroup_pointer(0x07ca), 0);
|
|
for (uint16_t id = 12; id <= 20; id++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(collision_record(id), RECORD_ACTIVE), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_record_countdowns(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
for (uint16_t id = 1; id <= 175; id++) {
|
|
Win16FarPtr counter = win16_far_add_offset(
|
|
window, (uint16_t)(0x61 + id));
|
|
uint8_t value = win16_read_u8(counter);
|
|
if (value != 0) {
|
|
value--;
|
|
win16_write_u8(counter, value);
|
|
if (value == 0) {
|
|
tdkpin_restore_record_b(frame_bp, id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_target_rotation(uint16_t frame_bp)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07c7)) == 0) {
|
|
return;
|
|
}
|
|
int32_t state = dgroup_i32(0x0855);
|
|
if (state < 6) {
|
|
write_dgroup_i32(0x0855, (int32_t)((uint32_t)state + 1u));
|
|
tdkpin_rotate_and_draw_targets(frame_bp);
|
|
} else {
|
|
win16_write_u8(win16_dgroup_pointer(0x07c7), 0);
|
|
}
|
|
}
|
|
|
|
static void publish_previous_position_from_slot(
|
|
Win16FarPtr window, uint16_t slot)
|
|
{
|
|
uint16_t stride = (uint16_t)(slot * 4u);
|
|
write_receiver_i32(window, 0x0bc2, dgroup_i32((uint16_t)(0x07e3 + stride)));
|
|
write_receiver_i32(window, 0x0bc6, dgroup_i32((uint16_t)(0x07eb + stride)));
|
|
}
|
|
|
|
static void restore_slot_region_or_clear_suppression(
|
|
uint16_t frame_bp, Win16FarPtr window, uint16_t slot)
|
|
{
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0be1)) == 0) {
|
|
publish_previous_position_from_slot(window, slot);
|
|
tdkpin_restore_previous_ball_region(frame_bp, 0);
|
|
} else {
|
|
win16_write_u8(win16_far_add_offset(window, 0x0be1), 0);
|
|
}
|
|
}
|
|
|
|
static void copy_ball_slot_one_to_zero(void)
|
|
{
|
|
write_dgroup_i32(0x07e3, dgroup_i32(0x07e7));
|
|
write_dgroup_i32(0x07eb, dgroup_i32(0x07ef));
|
|
write_dgroup_i32(0x07f3, dgroup_i32(0x07f7));
|
|
write_dgroup_i32(0x07fb, dgroup_i32(0x07ff));
|
|
write_dgroup_i32(0x0803, dgroup_i32(0x0807));
|
|
for (uint16_t index = 0; index < 6; index++) {
|
|
win16_write_u8(
|
|
win16_dgroup_pointer((uint16_t)(0x080b + index)),
|
|
win16_read_u8(win16_dgroup_pointer(
|
|
(uint16_t)(0x0811 + index))));
|
|
}
|
|
}
|
|
|
|
static void collapse_finished_multiball(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x61)) <= 1) {
|
|
return;
|
|
}
|
|
if (win16_read_u8(win16_dgroup_pointer(0x41f9)) == 0) {
|
|
restore_slot_region_or_clear_suppression(frame_bp, window, 0);
|
|
win16_write_u8(win16_far_add_offset(window, 0x61), 1);
|
|
win16_write_u8(win16_dgroup_pointer(0x0874), 0);
|
|
win16_write_u8(win16_dgroup_pointer(0x41f9), 1);
|
|
if (win16_read_u8(win16_dgroup_pointer(0x424c)) == 0) {
|
|
publish_previous_position_from_slot(window, 1);
|
|
tdkpin_finish_ball_or_advance_player(frame_bp);
|
|
} else {
|
|
copy_ball_slot_one_to_zero();
|
|
tdkpin_load_ball_slot(frame_bp, 0);
|
|
win16_write_u8(win16_dgroup_pointer(0x424c), 0);
|
|
}
|
|
} else if (win16_read_u8(win16_dgroup_pointer(0x424c)) == 0) {
|
|
restore_slot_region_or_clear_suppression(frame_bp, window, 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x61), 1);
|
|
win16_write_u8(win16_dgroup_pointer(0x0874), 0);
|
|
tdkpin_load_ball_slot(frame_bp, 0);
|
|
}
|
|
}
|
|
|
|
static void handle_ball_reset_request(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0be2)) != 1) {
|
|
return;
|
|
}
|
|
win16_write_u8(win16_far_add_offset(window, 0x0be2), 0);
|
|
tdkpin_save_ball_slot(frame_bp, 1);
|
|
tdkpin_update_dynamic_ball_record(frame_bp, 2);
|
|
tdkpin_reset_ball_state(frame_bp);
|
|
refresh_region(window, true, 20, 148, 243, 84);
|
|
tdkpin_save_ball_slot(frame_bp, 0);
|
|
tdkpin_update_dynamic_ball_record(frame_bp, 1);
|
|
}
|
|
|
|
static void handle_multiball_spawn_request(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0be0)) != 1) {
|
|
return;
|
|
}
|
|
tdkpin_save_ball_slot(frame_bp, 0);
|
|
tdkpin_update_dynamic_ball_record(frame_bp, 1);
|
|
write_dgroup_i32(0x07d3, dgroup_i32(0x3969));
|
|
write_dgroup_i32(0x07d7, dgroup_i32(0x396d));
|
|
write_receiver_i32(window, 0x0bc2, dgroup_i32(0x07d3));
|
|
write_receiver_i32(window, 0x0bc6, dgroup_i32(0x07d7));
|
|
tdkpin_restore_previous_ball_region(frame_bp, 0);
|
|
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
|
|
win16_write_u8(
|
|
win16_far_add_offset(window, (uint16_t)(player * 0x14u + 0x047b)), 0);
|
|
tdkpin_restore_record_b(frame_bp, 1020);
|
|
write_receiver_i32(window, 0x0baa, 0);
|
|
static const BorlandReal48 four_fifths =
|
|
{{0x80,0xcd,0xcc,0xcc,0xcc,0x4c}};
|
|
write_receiver_i32(
|
|
window,
|
|
0x0bae,
|
|
borland_real48_round_to_i32(borland_real48_multiply(
|
|
borland_i32_to_real48((int32_t)win16_read_u32(
|
|
win16_far_add_offset(window, 0x56))),
|
|
four_fifths)));
|
|
write_receiver_i32(window, 0x0bca, 0);
|
|
for (uint16_t index = 0; index < 6; index++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(window, (uint16_t)(0x0bce + index)), 0);
|
|
}
|
|
win16_write_u16(win16_dgroup_pointer(0x399a), 0, 2);
|
|
tdkpin_save_ball_slot(frame_bp, 1);
|
|
tdkpin_update_dynamic_ball_record(frame_bp, 2);
|
|
win16_write_u8(win16_dgroup_pointer(0x41f9), 1);
|
|
win16_write_u8(win16_dgroup_pointer(0x424c), 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x61), 2);
|
|
win16_write_u8(win16_dgroup_pointer(0x0874), 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0be0), 0);
|
|
}
|
|
|
|
static void active_ball_tick(uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
update_claw_state(frame_bp, window);
|
|
update_record_countdowns(frame_bp, window);
|
|
update_target_rotation(frame_bp);
|
|
collapse_finished_multiball(frame_bp, window);
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0bd8)) == 0 &&
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bdf)) == 0) {
|
|
tdkpin_simulate_ball_and_dispatch_collision(frame_bp);
|
|
}
|
|
handle_ball_reset_request(frame_bp, window);
|
|
handle_multiball_spawn_request(frame_bp, window);
|
|
}
|
|
|
|
static void update_intro_collision_visibility(uint16_t frame_bp, int32_t tick)
|
|
{
|
|
if (remainder_i32(tick, 2) == 0) {
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 32), 2);
|
|
if (phase >= 0 && phase <= 7) {
|
|
tdkpin_restore_record_or_item_a(
|
|
frame_bp, (uint16_t)(phase + 140));
|
|
} else if (phase >= 8 && phase <= 15) {
|
|
tdkpin_restore_record_b(
|
|
frame_bp, (uint16_t)(phase + 132));
|
|
}
|
|
}
|
|
if (remainder_i32(tick, 4) == 0) {
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 32), 4);
|
|
if (phase == 0) {
|
|
tdkpin_restore_record_or_item_a(frame_bp, 1001);
|
|
tdkpin_restore_record_b(frame_bp, 1002);
|
|
} else if (phase >= 1 && phase <= 4) {
|
|
tdkpin_restore_record_or_item_a(
|
|
frame_bp, (uint16_t)(phase + 1001));
|
|
tdkpin_restore_record_b(
|
|
frame_bp, (uint16_t)(phase + 1000));
|
|
} else if (phase >= 5 && phase <= 8) {
|
|
tdkpin_restore_record_or_item_a(
|
|
frame_bp, (uint16_t)(1009 - phase));
|
|
tdkpin_restore_record_b(
|
|
frame_bp, (uint16_t)(1010 - phase));
|
|
}
|
|
}
|
|
int32_t phase32 = remainder_i32(tick, 32);
|
|
if (phase32 == 0 || phase32 == 16) {
|
|
bool background_b = phase32 == 16;
|
|
uint16_t ids[] = {153, 154, 6};
|
|
for (size_t index = 0; index < sizeof(ids) / sizeof(ids[0]); index++) {
|
|
if (background_b) {
|
|
tdkpin_restore_record_b(frame_bp, ids[index]);
|
|
} else {
|
|
tdkpin_restore_record_or_item_a(frame_bp, ids[index]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_intro_record_strip(
|
|
Win16FarPtr window, int32_t tick)
|
|
{
|
|
if (remainder_i32(tick, 4) != 0) {
|
|
return;
|
|
}
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 40), 4);
|
|
bool background_b = phase >= 5;
|
|
int32_t normalized = background_b ? phase - 5 : phase;
|
|
uint16_t id = (uint16_t)(normalized * 3 + 90);
|
|
Win16FarPtr record = collision_record(id);
|
|
INT16 left = (INT16)win16_read_u16(
|
|
win16_far_add_offset(record, 0x4b));
|
|
INT16 top = (INT16)win16_read_u16(
|
|
win16_far_add_offset(record, 0x4d));
|
|
INT16 width = (INT16)(
|
|
win16_read_u16(win16_far_add_offset(record, 0x4f)) -
|
|
(uint16_t)left - 10u);
|
|
INT16 height = (INT16)(
|
|
win16_read_u16(win16_far_add_offset(record, 0x51)) -
|
|
(uint16_t)top + 1u);
|
|
refresh_region(window, background_b, height, width, top, left);
|
|
}
|
|
|
|
static void update_intro_four_point_chase(
|
|
Win16FarPtr window, int32_t tick)
|
|
{
|
|
if (remainder_i32(tick, 4) != 0) {
|
|
return;
|
|
}
|
|
static const INT16 y[4] = {209, 214, 219, 224};
|
|
static const INT16 x[4] = {150, 164, 178, 192};
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 16), 4);
|
|
if (phase < 0 || phase > 3) {
|
|
return;
|
|
}
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
tdkpin_restore_background_region_a(9, 9, y[phase], x[phase], dc);
|
|
tdkpin_present_background_region(9, 9, y[phase], x[phase], dc);
|
|
uint16_t previous = phase == 0 ? 3u : (uint16_t)(phase - 1);
|
|
tdkpin_restore_background_region_b(9, 9, y[previous], x[previous], dc);
|
|
tdkpin_present_background_region(9, 9, y[previous], x[previous], dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void update_intro_item_status(Win16FarPtr window, int32_t tick)
|
|
{
|
|
if (remainder_i32(tick, 8) != 0) {
|
|
return;
|
|
}
|
|
int32_t phase = quotient_i32(remainder_i32(tick, 144), 8);
|
|
int16_t item = (int16_t)(phase < 10 ? phase : 18 - phase);
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
tdkpin_draw_item_status(item, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void draw_intro_score_marquee(Win16FarPtr window)
|
|
{
|
|
uint32_t counter = win16_read_u32(win16_dgroup_pointer(0x087a)) + 1u;
|
|
win16_write_u32(win16_dgroup_pointer(0x087a), 0, counter);
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
HDC16 source = CreateCompatibleDC16(dc);
|
|
HDC16 temporary = CreateCompatibleDC16(dc);
|
|
HBITMAP16 bitmap = CreateCompatibleBitmap16(dc, 30, 284);
|
|
HGDIOBJ16 old_source = SelectObject16(
|
|
source, win16_read_u16(win16_dgroup_pointer(0x085f)));
|
|
HGDIOBJ16 old_temporary = SelectObject16(temporary, bitmap);
|
|
(void)BitBlt16(
|
|
temporary, 0, 0, 30, 100, source, 473, 277, 0x00cc0020u);
|
|
(void)BitBlt16(
|
|
temporary, 0, 100, 30, 137, source, 472, 233, 0x00cc0020u);
|
|
(void)SelectObject16(
|
|
source, win16_read_u16(win16_dgroup_pointer(0x0861)));
|
|
|
|
int32_t phase = remainder_i32((int32_t)counter, 49);
|
|
if (phase > 0 && phase < 22) {
|
|
(void)BitBlt16(
|
|
temporary,
|
|
0,
|
|
0,
|
|
30,
|
|
237,
|
|
source,
|
|
(INT16)(400 + (phase - 1) * 16),
|
|
0,
|
|
0x008800c6u);
|
|
} else {
|
|
INT16 split = (INT16)((phase - 22) * 16);
|
|
INT16 upper_height = (INT16)(237 - split);
|
|
(void)BitBlt16(
|
|
temporary,
|
|
0,
|
|
0,
|
|
30,
|
|
upper_height,
|
|
source,
|
|
(INT16)(400 + (phase - 1) * 16),
|
|
0,
|
|
0x008800c6u);
|
|
(void)BitBlt16(
|
|
temporary,
|
|
0,
|
|
upper_height,
|
|
30,
|
|
split,
|
|
source,
|
|
160,
|
|
0,
|
|
0x008800c6u);
|
|
}
|
|
(void)BitBlt16(
|
|
dc, 233, 372, 30, 237, temporary, 0, 0, 0x00cc0020u);
|
|
(void)SelectObject16(source, old_source);
|
|
(void)SelectObject16(temporary, old_temporary);
|
|
(void)DeleteObject16(bitmap);
|
|
(void)DeleteDC16(source);
|
|
(void)DeleteDC16(temporary);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void idle_transition_tick(uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07c9)) == 0) {
|
|
return;
|
|
}
|
|
int32_t tick = (int32_t)tick_count();
|
|
update_intro_collision_visibility(frame_bp, tick);
|
|
update_intro_record_strip(window, tick);
|
|
update_intro_four_point_chase(window, tick);
|
|
update_intro_item_status(window, tick);
|
|
if (remainder_i32(tick, 6) == 0) {
|
|
draw_intro_score_marquee(window);
|
|
}
|
|
}
|
|
|
|
static void update_panel_completion(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07c8)) == 0 ||
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bdd)) != 0) {
|
|
return;
|
|
}
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdf), 1);
|
|
int32_t frame = dgroup_i32(0x0851);
|
|
if (frame < 281) {
|
|
if (frame == 1) {
|
|
for (uint16_t id = 129; id <= 133; id++) {
|
|
win16_write_u16(
|
|
win16_far_add_offset(collision_record(id), 0x43), 0, 0);
|
|
}
|
|
}
|
|
write_dgroup_i32(0x0851, (int32_t)((uint32_t)frame + 1u));
|
|
tdkpin_draw_panel_animation(frame_bp);
|
|
return;
|
|
}
|
|
win16_write_u8(win16_dgroup_pointer(0x07c8), 0);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdf), 0);
|
|
write_dgroup_i32(0x0851, 0);
|
|
refresh_region(window, true, 70, 68, 41, 87);
|
|
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
|
|
for (uint16_t item = 15; item <= 19; item++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(
|
|
window,
|
|
(uint16_t)(player * 0x14u + item + 0x0467u)),
|
|
0);
|
|
}
|
|
}
|
|
|
|
static void initialize_game_if_requested(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
uint8_t player_count = win16_read_u8(win16_dgroup_pointer(0x07d0));
|
|
int16_t marker = (int16_t)win16_read_u16(win16_dgroup_pointer(
|
|
(uint16_t)(0x081f + player_count * 2u)));
|
|
if (marker != -1) {
|
|
return;
|
|
}
|
|
tdkpin_initialize_player_state(frame_bp);
|
|
tdkpin_redraw_all_players(frame_bp);
|
|
tdkpin_refresh_player_progress_markers(frame_bp);
|
|
tdkpin_reset_ball_state(frame_bp);
|
|
refresh_region(window, true, 20, 148, 243, 84);
|
|
win16_write_u32(win16_dgroup_pointer(0x0876), 0, 0);
|
|
}
|
|
|
|
void tdkpin_timer_tick_with_frame(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u16(win16_far_add_offset(window, 0x41)) != 0 ||
|
|
win16_read_u16(win16_far_add_offset(window, 0x43)) != 0 ||
|
|
win16_read_u16(win16_far_add_offset(window, 0x45)) != 0 ||
|
|
win16_read_u16(win16_far_add_offset(window, 0x47)) != 0 ||
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bd8)) != 0 ||
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bd9)) != 0 ||
|
|
win16_read_u8(win16_dgroup_pointer(0x07cc)) != 0 ||
|
|
win16_read_u8(win16_far_add_offset(window, 0x51)) != 0 ||
|
|
win16_read_u8(win16_dgroup_pointer(0x07c4)) == 0) {
|
|
return;
|
|
}
|
|
win16_write_u8(win16_far_add_offset(window, 0x51), 1);
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07c5)) != 0) {
|
|
for (uint16_t id = 1; id <= 175; id++) {
|
|
tdkpin_refresh_collision_record(frame_bp, id);
|
|
}
|
|
win16_write_u8(win16_dgroup_pointer(0x07c5), 0);
|
|
}
|
|
win16_write_u32(
|
|
win16_dgroup_pointer(0x0876), 0, tick_count() + 1u);
|
|
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0bdd)) != 0) {
|
|
active_ball_tick(frame_bp, window);
|
|
} else {
|
|
idle_transition_tick(frame_bp, window);
|
|
update_panel_completion(frame_bp, window);
|
|
initialize_game_if_requested(frame_bp, window);
|
|
}
|
|
|
|
tdkpin_update_flippers(frame_bp);
|
|
tdkpin_drain_messages_except_input();
|
|
win16_write_u8(win16_far_add_offset(window, 0x51), 0);
|
|
uint16_t tilt_counter = win16_read_u16(
|
|
win16_far_add_offset(window, 0x5e));
|
|
if (tilt_counter != 0) {
|
|
/* 1000:fd73 reads game_tilted but both branches converge here. */
|
|
(void)win16_read_u8(win16_dgroup_pointer(0x07c6));
|
|
win16_write_u16(window, 0x5e, (uint16_t)(tilt_counter - 1u));
|
|
}
|
|
}
|
|
|
|
void tdkpin_timer_tick(
|
|
Win16FarPtr window, uint32_t ignored_callback_argument)
|
|
{
|
|
(void)ignored_callback_argument;
|
|
tdkpin_timer_tick_with_frame(win16_current_stack_pointer(), window);
|
|
}
|