From e6a84fd26bad3880658c0f1698f20ce2f9216443 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 26 Feb 2017 15:12:31 -0500 Subject: [PATCH] Attempted a hardware-correct implementation of missile-to-player latching. This completes the last of the **knowing** inaccuracies. The rest are as-of-yet unwitting. --- Machines/Atari2600/TIA.cpp | 41 +++++++++++++++++++++++++------------- Machines/Atari2600/TIA.hpp | 37 +++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/Machines/Atari2600/TIA.cpp b/Machines/Atari2600/TIA.cpp index 262201d55..15fb2e939 100644 --- a/Machines/Atari2600/TIA.cpp +++ b/Machines/Atari2600/TIA.cpp @@ -354,10 +354,8 @@ void TIA::set_missile_position(int missile) void TIA::set_missile_position_to_player(int missile, bool lock) { assert(missile >= 0 && missile < 2); - // TODO: implement this correctly; should be triggered by player counter hitting the appropriate point, and - // use additional storage position for enabled - if(missile_[missile].locked_to_player && !lock) missile_[missile].position = player_[missile].position + 1 + 16/player_[missile].adder; missile_[missile].locked_to_player = lock; + player_[missile].latched_pixel4_time = -1; } void TIA::set_missile_motion(int missile, uint8_t motion) @@ -450,8 +448,8 @@ void TIA::output_for_cycles(int number_of_cycles) draw_playfield(latent_start, latent_end); draw_object(player_[0], (uint8_t)CollisionType::Player0, output_cursor, horizontal_counter_); draw_object(player_[1], (uint8_t)CollisionType::Player1, output_cursor, horizontal_counter_); - draw_object(missile_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_); - draw_object(missile_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_); + draw_missile(missile_[0], player_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_); + draw_missile(missile_[1], player_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_); draw_object(ball_, (uint8_t)CollisionType::Ball, output_cursor, horizontal_counter_); // convert to television signals @@ -642,7 +640,7 @@ template void TIA::perform_motion_step(T &object) else if(object.position == 15 && object.copy_flags&1) object.reset_pixels(1); else if(object.position == 31 && object.copy_flags&2) object.reset_pixels(2); else if(object.position == 63 && object.copy_flags&4) object.reset_pixels(3); - else object.skip_pixels(1); + else object.skip_pixels(1, object.motion_time); object.position = (object.position + 1) % 160; object.motion_step --; object.motion_time += 4; @@ -725,25 +723,23 @@ template void TIA::draw_object_visible(T &object, const uint8_t collisi // the decision is to progress by length const int length = next_event_time - start; - // TODO: the problem with this is that it uses the enabled/pixel state of each object four cycles early; - // an appropriate solution would probably be to capture the drawing request into a queue and honour them outside - // this loop, clipped to the real output parameters. Assuming all state consumed by draw_pixels is captured, - // and mutated now then also queueing resets and skips shouldn't be necessary. + // enqueue a future intention to draw pixels if spitting them out now would violate accuracy; + // otherwise draw them now if(object.enqueues && next_event_time > time_now) { if(start < time_now) { - object.output_pixels(&collision_buffer_[start], time_now - start, collision_identity); - object.enqueue_pixels(time_now, next_event_time); + object.output_pixels(&collision_buffer_[start], time_now - start, collision_identity, start + first_pixel_cycle - 4); + object.enqueue_pixels(time_now, next_event_time, time_now + first_pixel_cycle - 4); } else { - object.enqueue_pixels(start, next_event_time); + object.enqueue_pixels(start, next_event_time, start + first_pixel_cycle - 4); } } else { - object.output_pixels(&collision_buffer_[start], length, collision_identity); + object.output_pixels(&collision_buffer_[start], length, collision_identity, start + first_pixel_cycle - 4); } // the next interesting event is after next_event_time cycles, so progress @@ -763,3 +759,20 @@ template void TIA::draw_object_visible(T &object, const uint8_t collisi } } } + +#pragma mark - Missile drawing + +void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) +{ + if(!missile.locked_to_player || player.latched_pixel4_time < 0) + { + draw_object(missile, collision_identity, start, end); + } + else + { + draw_object(missile, collision_identity, start, player.latched_pixel4_time); + missile.position = 0; + draw_object(missile, collision_identity, player.latched_pixel4_time, end); + player.latched_pixel4_time = -1; + } +} diff --git a/Machines/Atari2600/TIA.hpp b/Machines/Atari2600/TIA.hpp index 90148258d..a462fb54f 100644 --- a/Machines/Atari2600/TIA.hpp +++ b/Machines/Atari2600/TIA.hpp @@ -159,22 +159,29 @@ class TIA { int graphic_index; int pixel_position; + int latched_pixel4_time; const bool enqueues = true; - inline void skip_pixels(const int count) + inline void skip_pixels(const int count, int from_horizontal_counter) { + int old_pixel_position = pixel_position; pixel_position = std::min(32, pixel_position + count * adder); + if(!copy_index_ && old_pixel_position < 16 && pixel_position >= 16) + { + latched_pixel4_time = from_horizontal_counter + (16 - old_pixel_position) / adder; + } } inline void reset_pixels(int copy) { pixel_position = 0; + copy_index_ = copy; } - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity) + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask); - skip_pixels(count); + skip_pixels(count, from_horizontal_counter); } void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) @@ -198,7 +205,7 @@ class TIA { } } - void enqueue_pixels(const int start, const int end) + void enqueue_pixels(const int start, const int end, int from_horizontal_counter) { queue_[queue_write_pointer_].start = start; queue_[queue_write_pointer_].end = end; @@ -206,10 +213,11 @@ class TIA { queue_[queue_write_pointer_].adder = adder; queue_[queue_write_pointer_].reverse_mask = reverse_mask; queue_write_pointer_ = (queue_write_pointer_ + 1)&3; - skip_pixels(end - start); + skip_pixels(end - start, from_horizontal_counter); } private: + int copy_index_; struct QueuedPixels { int start, end; @@ -240,7 +248,7 @@ class TIA { int size; const bool enqueues = false; - inline void skip_pixels(const int count) + inline void skip_pixels(const int count, int from_horizontal_counter) { pixel_position = std::max(0, pixel_position - count); } @@ -250,7 +258,7 @@ class TIA { pixel_position = size; } - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity) + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { int output_cursor = 0; while(pixel_position && output_cursor < count) @@ -262,7 +270,7 @@ class TIA { } void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {} - void enqueue_pixels(const int start, const int end) {} + void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {} HorizontalRun() : pixel_position(0), size(1) {} }; @@ -274,16 +282,16 @@ class TIA { bool locked_to_player; int copy_flags; - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity) + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { if(!pixel_position) return; if(enabled && !locked_to_player) { - HorizontalRun::output_pixels(target, count, collision_identity); + HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); } else { - skip_pixels(count); + skip_pixels(count, from_horizontal_counter); } } @@ -296,16 +304,16 @@ class TIA { int enabled_index; const int copy_flags = 0; - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity) + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { if(!pixel_position) return; if(enabled[enabled_index]) { - HorizontalRun::output_pixels(target, count, collision_identity); + HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); } else { - skip_pixels(count); + skip_pixels(count, from_horizontal_counter); } } @@ -318,6 +326,7 @@ class TIA { template void perform_motion_step(T &object); // drawing methods and state + void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end); template void draw_object(T &, const uint8_t collision_identity, int start, int end); template void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now); inline void draw_playfield(int start, int end);