mirror of https://github.com/stella-emu/stella.git
More TIA documentation.
This commit is contained in:
parent
e7d85f2d6b
commit
6c1efce278
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue