More TIA documentation.

This commit is contained in:
Christian Speckner 2019-04-22 23:24:28 +02:00
parent e7d85f2d6b
commit 6c1efce278
3 changed files with 236 additions and 22 deletions

View File

@ -37,7 +37,7 @@ void Ball::reset()
myIsEnabledNew = false;
myIsEnabled = false;
myIsDelaying = false;
myIsVisible = false;
mySignalActive = false;
myHmmClocks = 0;
myCounter = 0;
isMoving = false;
@ -168,8 +168,11 @@ void Ball::startMovement()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Ball::nextLine()
{
myIsVisible = myIsRendering && myRenderCounter >= 0;
collision = (myIsVisible && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
// Reevalute the collision mask in order to properly account for collisions during
// hblank. Usually, this will be taken care off in the next tick, but there is no
// next tick before hblank ends.
mySignalActive = myIsRendering && myRenderCounter >= 0;
collision = (mySignalActive && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -199,7 +202,7 @@ void Ball::updateEnabled()
{
myIsEnabled = !myIsSuppressed && (myIsDelaying ? myIsEnabledOld : myIsEnabledNew);
collision = (myIsVisible && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
collision = (mySignalActive && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
myTIA->scheduleCollisionUpdate();
}
@ -259,7 +262,7 @@ bool Ball::save(Serializer& out) const
out.putBool(myIsEnabled);
out.putBool(myIsSuppressed);
out.putBool(myIsDelaying);
out.putBool(myIsVisible);
out.putBool(mySignalActive);
out.putByte(myHmmClocks);
out.putByte(myCounter);
@ -300,7 +303,7 @@ bool Ball::load(Serializer& in)
myIsEnabled = in.getBool();
myIsSuppressed = in.getBool();
myIsDelaying = in.getBool();
myIsVisible = in.getBool();
mySignalActive = in.getBool();
myHmmClocks = in.getByte();
myCounter = in.getByte();

View File

@ -28,52 +28,134 @@ class Ball : public Serializable
{
public:
/**
The collision mask is injected at construction
*/
explicit Ball(uInt32 collisionMask);
public:
/**
Set the TIA instance
*/
void setTIA(TIA* tia) { myTIA = tia; }
/**
Reset to initial state.
*/
void reset();
/**
ENABL write.
*/
void enabl(uInt8 value);
/**
HMBL write.
*/
void hmbl(uInt8 value);
/**
RESBL write.
*/
void resbl(uInt8 counter);
/**
CTRLPF write.
*/
void ctrlpf(uInt8 value);
/**
VDELBL write.
*/
void vdelbl(uInt8 value);
void toggleCollisions(bool enabled);
/**
Enable / disable ball display (debugging only, not used during normal emulation).
*/
void toggleEnabled(bool enabled);
/**
Enable / disable ball collisions (debugging only, not used during normal emulation).
*/
void toggleCollisions(bool enabled);
/**
Set color PF.
*/
void setColor(uInt8 color);
/**
Set the color used in "debug colors" mode.
*/
void setDebugColor(uInt8 color);
/**
Enable "debug colors" mode.
*/
void enableDebugColors(bool enabled);
/**
Update internal state to use the color loss palette.
*/
void applyColorLoss();
/**
Switch to "inverted phase" mode. This mode emulates the phase shift
between movement and ordinary clock pulses that is exhibited by some
TIA revisions and that give rise to glitches like the infamous Cool
Aid Man bug on some Jr. models.
*/
void setInvertedPhaseClock(bool enable);
/**
Start movement --- this is triggered by strobing HMOVE.
*/
void startMovement();
/**
Notify ball of line change.
*/
void nextLine();
/**
Is the ball visible? This is determined by looking at bit 15
of the collision mask.
*/
bool isOn() const { return (collision & 0x8000); }
/**
Get the current color.
*/
uInt8 getColor() const { return myColor; }
/**
Shuffle the enabled flag. This is called in VDELBL mode when GRP1 is
written (with a delay of one cycle).
*/
void shuffleStatus();
/**
Calculate the sprite position from the counter. Used by the debugger only.
*/
uInt8 getPosition() const;
/**
Set the counter and place the sprite at a specified position. Used by the debugger
only.
*/
void setPosition(uInt8 newPosition);
/**
Get the "old" and "new" values of the enabled flag. Used by the debuggger only.
*/
bool getENABLOld() const { return myIsEnabledOld; }
bool getENABLNew() const { return myIsEnabledNew; }
/**
Directly set the "old" value of the enabled flag. Used by the debugger only.
*/
void setENABLOld(bool enabled);
/**
@ -82,54 +164,168 @@ class Ball : public Serializable
bool save(Serializer& out) const override;
bool load(Serializer& in) override;
/**
Process a single movement tick. Inline for performance (implementation below).
*/
inline void movementTick(uInt32 clock, bool hblank);
inline void tick(bool isReceivingMclock = true);
/**
Tick one color clock. Inline for performance (implementation below).
*/
inline void tick(bool isReceivingRegularClock = true);
public:
/**
16 bit Collision mask. Each sprite is represented by a single bit in the mask
(1 = active, 0 = inactive). All other bits are always 1. The highest bit is
abused to store visibility (as the actual collision bit will always be zero
if collisions are disabled).
*/
uInt32 collision;
/**
The movement flag. This corresponds to the state of the movement latch for
this sprite --- true while movement is active and ticks are still propagated
to the counters, false otherwise.
*/
bool isMoving;
private:
/**
Recalculate enabled / disabled state. This is not the same as the enabled / disabled
flag, but rather calculated from the flag and the corresponding debug setting.
*/
void updateEnabled();
/**
Recalculate ball color based on COLUPF, debug colors, color loss, etc.
*/
void applyColors();
private:
/**
Offset of the render counter when rendering starts. Actual display starts at zero,
so this amounts to a delay.
*/
enum Count: Int8 {
renderCounterOffset = -4
};
private:
/**
Collision mask values for active / inactive states. Disabling collisions
will change those.
*/
uInt32 myCollisionMaskDisabled;
uInt32 myCollisionMaskEnabled;
/**
Color value calculated by applyColors().
*/
uInt8 myColor;
uInt8 myObjectColor, myDebugColor;
/**
Color configured by COLUPF
*/
uInt8 myObjectColor;
/**
Color for debug mode.
*/
uInt8 myDebugColor;
/**
Debug mode enabled?
*/
bool myDebugEnabled;
/**
"old" and "new" values of the enabled flag.
*/
bool myIsEnabledOld;
bool myIsEnabledNew;
bool myIsEnabled;
bool myIsSuppressed;
bool myIsDelaying;
bool myIsVisible;
/**
Actual value of the enabled flag. Determined from the "old" and "new" values
VDEL, debug settings etc.
*/
bool myIsEnabled;
/**
Is the sprite turned off in the debugger?
*/
bool myIsSuppressed;
/**
Is VDEL active?
*/
bool myIsDelaying;
/**
Is the ball sprite signal currently active?
*/
bool mySignalActive;
/**
HMM clocks before movement stops. Changed by writing to HMBL.
*/
uInt8 myHmmClocks;
/**
The sprite counter
*/
uInt8 myCounter;
/**
Ball width, as configured by CTRLPF.
*/
uInt8 myWidth;
/**
Effective width used for drawing. This is usually the same as myWidth,
but my differ in starfield mode.
*/
uInt8 myEffectiveWidth;
/**
The value of the counter value at which the last movement tick occurred. This is
used for simulating the starfield pattern.
*/
uInt8 myLastMovementTick;
/**
Are we currently rendering? This is latched when the counter hits it decode value,
or when RESBL is strobed. It is turned off once the render counter reaches its
maximum (i.e. when the sprite has been fully displayed).
*/
bool myIsRendering;
/**
Rendering counter. It starts counting (below zero) when the counter hits the decode value,
and the actual signal becomes active once it reaches 0.
*/
Int8 myRenderCounter;
/**
This memorizes a movement tick outside HBLANK in inverted clock mode. It is latched
durin ::movementTick() and processed during ::tick() where it inhibits the clock
pulse.
*/
bool myInvertedPhaseClock;
/**
Use "inverted movement clock phase" mode? This emulates an idiosyncracy of several
newer TIA revisions (see the setter above for a deeper explanation).
*/
bool myUseInvertedPhaseClock;
/**
TIA instance. Required for flushing the line cache and requesting collision updates.
*/
TIA* myTIA;
private:
@ -149,34 +345,49 @@ void Ball::movementTick(uInt32 clock, bool hblank)
{
myLastMovementTick = myCounter;
// Stop movement once the number of clocks according to HMBL is reached
if (clock == myHmmClocks)
isMoving = false;
if(isMoving)
{
// Process the tick if we are in hblank. Otherwise, the tick is either masked
// by an ordinary tick or merges two consecutive ticks into a single tick (inverted
// movement clock phase mode).
if (hblank) tick(false);
// Track a tick outside hblank for later processing
myInvertedPhaseClock = !hblank;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Ball::tick(bool isReceivingMclock)
void Ball::tick(bool isReceivingRegularClock)
{
// If we are in inverted movement clock phase mode and a movement tick occurred, it
// will supress the tick.
if(myUseInvertedPhaseClock && myInvertedPhaseClock)
{
myInvertedPhaseClock = false;
return;
}
myIsVisible = myIsRendering && myRenderCounter >= 0;
collision = (myIsVisible && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
// Turn on the signal if the render counter reaches the threshold
mySignalActive = myIsRendering && myRenderCounter >= 0;
bool starfieldEffect = isMoving && isReceivingMclock;
// Consider enabled status and the signal to determine visibility (as represented
// by the collision mask)
collision = (mySignalActive && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
// Regular clock pulse during movement -> starfield mode
bool starfieldEffect = isMoving && isReceivingRegularClock;
// Decode value that triggers rendering
if (myCounter == 156) {
myIsRendering = true;
myRenderCounter = renderCounterOffset;
// What follows is an effective description of ball width in starfield mode.
uInt8 starfieldDelta = (myCounter + TIAConstants::H_PIXEL - myLastMovementTick) % 4;
if (starfieldEffect && starfieldDelta == 3 && myWidth < 4) ++myRenderCounter;

View File

@ -110,7 +110,7 @@ class Playfield : public Serializable
void nextLine();
/**
Is the playfield signal active? This is determined by looking at bit 8
Is the playfield visible? This is determined by looking at bit 15
of the collision mask.
*/
bool isOn() const { return (collision & 0x8000); }
@ -136,8 +136,8 @@ class Playfield : public Serializable
/**
16 bit Collision mask. Each sprite is represented by a single bit in the mask
(1 = active, 0 = inactive). All other bits are always 1. The highest bit is
abused to store the active / inactive state (as the actual collision bit will
always be zero if collisions are disabled).
abused to store visibility (as the actual collision bit will always be zero
if collisions are disabled).
*/
uInt32 collision;
@ -234,7 +234,7 @@ class Playfield : public Serializable
uInt32 myX;
/**
TIA instance.
TIA instance. Required for flushing the line cache.
*/
TIA* myTIA;