/* * HISTORICAL PARTIAL fixed-point physics transcription for TDKPIN.EXE. * * Authority: * SHA-256 a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c * * Primary raw routine: * FUN_1000_c79c at 1000:c79c (movement, object scan, response dispatch) * * Supporting routines: * FUN_1000_638e / FUN_1000_6bf8 (launcher key release/press) * FUN_1000_7ed9 / FUN_1000_8b0d (moving flipper records) * FUN_1000_b476 (rule flags) * * This superseded intermediate is retained only as provenance for earlier * static/live comparison work and is not part of the completed deliverable. * The complete 1000:c79c implementation is reconstructed/tdkpin_physics.c; * its rule and persistent-contact dependencies are likewise represented by * address-linked verified modules under reconstructed/. */ #include #include #include #include enum { PHYSICS_SUBSTEP_MILLISECONDS = 10, GRAVITY_MILLIPIXELS_PER_SUBSTEP = 15, MAX_SPEED_MILLIPIXELS_PER_SUBSTEP = 3800, LAUNCHER_IMPULSE_MILLIPIXELS = 375, COLLISION_OBJECT_COUNT = 175, }; typedef struct { int32_t x; int32_t y; } Vec2Milli; typedef enum { OBJECT_TYPE_CIRCLE = 1, OBJECT_TYPE_LINE = 2, OBJECT_TYPE_LOCK_OR_MECHANISM = 3, OBJECT_TYPE_TARGET_SENSOR = 4, } CollisionObjectType; /* Semantic view of one initialized 0x53-byte record from OBJECTS.tsv. */ typedef struct { uint8_t id; CollisionObjectType type; /* record +0x00 relative to field base 0x095b */ uint8_t subtype; /* +0x01 */ bool active; /* +0x34 */ Vec2Milli bounds_min; /* +0x02 / +0x06 */ Vec2Milli bounds_max; /* +0x0a / +0x0e */ Vec2Milli point1; /* +0x12 / +0x16 */ Vec2Milli point2; /* +0x1a / +0x1e */ int32_t radius; /* Real48 at +0x22, decoded to millipixels */ double normal_rebound; /* Real48 at +0x28 */ double tangent_coupling; /* Real48 at +0x2e */ double auxiliary; /* Real48 at +0x35 */ double normal_kick; /* Real48 at +0x3b */ uint16_t flags; /* +0x41 */ uint16_t contact_state; /* +0x43 */ uint32_t score; /* +0x45 / +0x47 */ uint16_t layer_mask; /* +0x49 */ } CollisionRecord; typedef struct { Vec2Milli position; /* DAT_1028_07d3 / DAT_1028_07d7 */ Vec2Milli predicted; /* DAT_1028_07db / DAT_1028_07df */ Vec2Milli velocity; /* window object +0xbaa / +0xbae */ Vec2Milli previous_position; /* window object +0xbba / +0xbbe */ } BallPhysics; typedef struct { bool hit; double progress; Vec2Milli response_velocity; uint8_t object_id; } CollisionCandidate; static int64_t cross(Vec2Milli left, Vec2Milli right) { return (int64_t)left.x * right.y - (int64_t)left.y * right.x; } static Vec2Milli subtract(Vec2Milli left, Vec2Milli right) { return (Vec2Milli){left.x - right.x, left.y - right.y}; } static Vec2Milli add(Vec2Milli left, Vec2Milli right) { return (Vec2Milli){left.x + right.x, left.y + right.y}; } /* Type-2 branch inside 1000:c79c. The registered direction is significant. */ static CollisionCandidate line_candidate( Vec2Milli old_position, Vec2Milli velocity, const CollisionRecord *record) { Vec2Milli line = subtract(record->point2, record->point1); Vec2Milli from_ball = subtract(record->point1, old_position); int64_t denominator = cross(velocity, line); int64_t path_numerator; int64_t line_numerator; double length; double tangent_x; double tangent_y; double normal_x; double normal_y; double normal_speed; double tangent_speed; double outgoing_normal; double outgoing_tangent; if (denominator == 0) { return (CollisionCandidate){0}; } path_numerator = cross(from_ball, line); line_numerator = cross(from_ball, velocity); if (denominator > 0) { if (path_numerator <= 0 || path_numerator > denominator || line_numerator < 0 || line_numerator > denominator) { return (CollisionCandidate){0}; } } else if (path_numerator >= 0 || path_numerator < denominator || line_numerator > 0 || line_numerator < denominator) { return (CollisionCandidate){0}; } length = hypot((double)line.x, (double)line.y); if (length == 0.0) { return (CollisionCandidate){0}; } tangent_x = line.x / length; tangent_y = line.y / length; normal_x = -tangent_y; normal_y = tangent_x; normal_speed = velocity.x * normal_x + velocity.y * normal_y; if (normal_speed <= 0.0) { /* back side is a one-way pass */ return (CollisionCandidate){0}; } tangent_speed = velocity.x * tangent_x + velocity.y * tangent_y; outgoing_normal = -record->normal_rebound * normal_speed; outgoing_tangent = tangent_speed + record->tangent_coupling * normal_speed; return (CollisionCandidate){ .hit = true, .progress = (double)path_numerator / (double)denominator, .response_velocity = { (int32_t)llround(normal_x * outgoing_normal + tangent_x * outgoing_tangent), (int32_t)llround(normal_y * outgoing_normal + tangent_y * outgoing_tangent), }, .object_id = record->id, }; } /* Type-1 branch inside 1000:c79c. */ static CollisionCandidate circle_candidate( Vec2Milli old_position, Vec2Milli velocity, const CollisionRecord *record) { Vec2Milli offset = subtract(old_position, record->point1); int64_t old_distance_squared = (int64_t)offset.x * offset.x + (int64_t)offset.y * offset.y; int64_t radius_squared = (int64_t)record->radius * record->radius; double a; double b; double c; double discriminant; double progress; double hit_x; double hit_y; double hit_length; double normal_x; double normal_y; double tangent_x; double tangent_y; double normal_speed; double tangent_speed; double outgoing_normal; double outgoing_tangent; if (old_distance_squared <= radius_squared) { return (CollisionCandidate){0}; /* persistent state suppresses repeats */ } a = (double)velocity.x * velocity.x + (double)velocity.y * velocity.y; if (a == 0.0) { return (CollisionCandidate){0}; } b = 2.0 * ((double)offset.x * velocity.x + (double)offset.y * velocity.y); c = (double)old_distance_squared - (double)radius_squared; discriminant = b * b - 4.0 * a * c; if (discriminant < 0.0) { return (CollisionCandidate){0}; } progress = (-b - sqrt(discriminant)) / (2.0 * a); if (progress <= 0.0 || progress > 1.0) { return (CollisionCandidate){0}; } hit_x = offset.x + velocity.x * progress; hit_y = offset.y + velocity.y * progress; hit_length = hypot(hit_x, hit_y); if (hit_length == 0.0) { return (CollisionCandidate){0}; } normal_x = hit_x / hit_length; normal_y = hit_y / hit_length; tangent_x = normal_y; /* clockwise tangent */ tangent_y = -normal_x; normal_speed = velocity.x * normal_x + velocity.y * normal_y; if (normal_speed >= 0.0) { return (CollisionCandidate){0}; } tangent_speed = velocity.x * tangent_x + velocity.y * tangent_y; outgoing_normal = -record->normal_rebound * normal_speed + record->normal_kick * MAX_SPEED_MILLIPIXELS_PER_SUBSTEP; outgoing_tangent = tangent_speed - record->tangent_coupling * normal_speed; return (CollisionCandidate){ .hit = true, .progress = progress, .response_velocity = { (int32_t)llround(normal_x * outgoing_normal + tangent_x * outgoing_tangent), (int32_t)llround(normal_y * outgoing_normal + tangent_y * outgoing_tangent), }, .object_id = record->id, }; } static void begin_substep(BallPhysics *ball) { double speed; ball->velocity.y += GRAVITY_MILLIPIXELS_PER_SUBSTEP; speed = hypot((double)ball->velocity.x, (double)ball->velocity.y); if (speed > MAX_SPEED_MILLIPIXELS_PER_SUBSTEP) { double scale = MAX_SPEED_MILLIPIXELS_PER_SUBSTEP / speed; ball->velocity.x = (int32_t)llround(ball->velocity.x * scale); ball->velocity.y = (int32_t)llround(ball->velocity.y * scale); } ball->previous_position = ball->position; ball->predicted = add(ball->position, ball->velocity); } static void apply_candidate(BallPhysics *ball, CollisionCandidate candidate) { if (candidate.hit) { ball->velocity = candidate.response_velocity; } /* The original advances from the previous position, not the hit point. */ ball->position = add(ball->previous_position, ball->velocity); } /* Keyboard-repeat behavior recovered from 1000:6bf8 and 1000:638e. */ static void launcher_down_event(BallPhysics *ball) { ball->velocity.x = 0; ball->velocity.y -= LAUNCHER_IMPULSE_MILLIPIXELS; } /* random_less_than_3800 is Borland random(3800), FUN_1020_14a4. */ static void launcher_release_event(BallPhysics *ball, uint16_t random_less_than_3800) { launcher_down_event(ball); /* release applies one additional impulse */ if (ball->velocity.y < -MAX_SPEED_MILLIPIXELS_PER_SUBSTEP) { ball->velocity.y = -MAX_SPEED_MILLIPIXELS_PER_SUBSTEP + random_less_than_3800 / 40; } } /* Keep pure helpers referenced for strict syntax/warning checks. */ typedef struct { CollisionCandidate (*line)(Vec2Milli, Vec2Milli, const CollisionRecord *); CollisionCandidate (*circle)(Vec2Milli, Vec2Milli, const CollisionRecord *); void (*begin)(BallPhysics *); void (*apply)(BallPhysics *, CollisionCandidate); void (*launcher_down)(BallPhysics *); void (*launcher_release)(BallPhysics *, uint16_t); } RestoredPhysicsFunctions; const RestoredPhysicsFunctions TDKPIN_RESTORED_PHYSICS = { line_candidate, circle_candidate, begin_substep, apply_candidate, launcher_down_event, launcher_release_event, };