- implemented trigger lists for missile/players
	- implemented player sprite
This commit is contained in:
steve 2018-06-15 08:34:37 +01:00
parent 21dbb029f4
commit a16ca04b12
6 changed files with 131 additions and 32 deletions

View file

@ -36,6 +36,8 @@ const (
SubKeywordBreaks = "BREAKS"
SubKeywordTraps = "TRAPS"
SubKeywordVideo = "VIDEO"
SubKeywordCPU = "CPU"
)
// DebuggerCommands provides:
@ -44,7 +46,7 @@ const (
var DebuggerCommands = parser.Commands{
KeywordInsert: parser.CommandArgs{parser.Arg{Typ: parser.ArgFile, Req: true}},
KeywordBreak: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgNumber, Req: true}},
KeywordTrap: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgNumber, Req: true}},
KeywordTrap: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}},
KeywordOnHalt: parser.CommandArgs{parser.Arg{Typ: parser.ArgIndeterminate, Req: false}},
KeywordList: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: true, Vals: parser.Keywords{SubKeywordBreaks, SubKeywordTraps}}},
KeywordClear: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: true, Vals: parser.Keywords{SubKeywordBreaks, SubKeywordTraps}}},
@ -53,7 +55,7 @@ var DebuggerCommands = parser.Commands{
KeywordReset: parser.CommandArgs{},
KeywordRun: parser.CommandArgs{},
KeywordStep: parser.CommandArgs{},
KeywordStepMode: parser.CommandArgs{},
KeywordStepMode: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: false, Vals: parser.Keywords{SubKeywordCPU, SubKeywordVideo}}},
KeywordTerse: parser.CommandArgs{},
KeywordVerbose: parser.CommandArgs{},
KeywordVerbosity: parser.CommandArgs{},

View file

@ -12,6 +12,7 @@ type missileSprite struct {
size uint8
enable bool
futureEnable *future
triggerList []int
lateStartDraw bool
}
@ -61,7 +62,7 @@ func (ms missileSprite) MachineInfo() string {
// tick moves the counters along for the missile sprite
func (ms *missileSprite) tick() {
// position
if ms.tickPosition(nil) {
if ms.tickPosition(ms.triggerList) {
if ms.futureReset.isScheduled() {
ms.stopDrawing()
ms.lateStartDraw = true

View file

@ -1,7 +1,9 @@
package video
import (
"fmt"
"gopher2600/hardware/tia/colorclock"
"math/bits"
)
type playerSprite struct {
@ -10,8 +12,12 @@ type playerSprite struct {
color uint8
gfxData uint8
gfxDataPrev uint8
reflection bool
size uint8
reflected bool
verticalDelay bool
triggerList []int
lateStartDraw bool
}
func newPlayerSprite(label string, colorClock *colorclock.ColorClock) *playerSprite {
@ -20,16 +26,83 @@ func newPlayerSprite(label string, colorClock *colorclock.ColorClock) *playerSpr
return ps
}
func (ps playerSprite) MachineInfoTerse() string {
gfxData := ps.gfxData
if ps.verticalDelay {
gfxData = ps.gfxDataPrev
}
ref := " "
if ps.reflected {
ref = "r"
}
return fmt.Sprintf("%s gfx: %s %08b", ps.sprite.MachineInfoTerse(), ref, gfxData)
}
// nothing to add to sprite type implementation of MachineInfo() and
// MachineInfoTerse()
// tick moves the counters along for the player sprite
func (ps *playerSprite) tick() {
// position
if ps.tickPosition(ps.triggerList) {
if ps.futureReset.isScheduled() {
ps.stopDrawing()
ps.lateStartDraw = true
} else {
ps.startDrawing()
}
} else {
// if player.position.tick() has not caused the position counter to
// cycle then progress draw signal according to color clock phase and
// nusiz_player_width. for nusiz_player_width and 0b101 and 0b111,
// pixels are smeared over additional cycles in order to create the
// double and quadruple sized sprites
//
// NOTE: the key difference between player and missile ticking is the
// absence in the player sprite, of a filter on draw sig ticking when
// there a position reset is pending
if ps.size == 0x05 {
if ps.colorClock.Phase == 0 || ps.colorClock.Phase == 2 {
ps.tickDrawSig()
}
} else if ps.size == 0x07 {
if ps.colorClock.Phase == 2 {
ps.tickDrawSig()
}
} else {
ps.tickDrawSig()
}
}
// reset
if ps.futureReset.tick() {
ps.resetPosition()
if ps.lateStartDraw {
ps.startDrawing()
ps.lateStartDraw = false
}
}
}
// pixel returns the color of the player at the current time. returns
// (false, 0) if no pixel is to be seen; and (true, col) if there is
func (ps *playerSprite) pixel() (bool, uint8) {
// vertical delay
gfxData := ps.gfxData
if ps.verticalDelay {
gfxData = ps.gfxDataPrev
}
if ps.reflected {
gfxData = bits.Reverse8(gfxData)
}
if ps.drawSigCount > 0 && ps.drawSigCount <= ps.drawSigMax {
if gfxData>>(uint8(ps.drawSigMax)-uint8(ps.drawSigCount))&0x01 == 0x01 {
return true, ps.color
}
}
return false, 0
}
@ -40,3 +113,6 @@ func (ps *playerSprite) scheduleReset(hblank *bool) {
ps.futureReset.schedule(delayResetSprite, true)
}
}
func (ps *playerSprite) scheduleReflection() {
}

View file

@ -25,15 +25,19 @@ type sprite struct {
// position of the sprite as a polycounter value - the basic principle
// behind VCS sprites is to begin drawing of the sprite when position
// circulates to 0000000
position polycounter.Polycounter
position polycounter.Polycounter
// reset position of the sprite -- does not take horizonal movement into
// account
positionResetPixel int
// the draw signal controls which "bit" of the sprite is to be drawn next.
// generally, the draw signal is activated when the position polycounter
// matches the colorClock polycounter, but differenct sprite types handle
// this differently in certain circumstances
drawSigMax int
drawSigCount int
drawSigMax int
drawSigOff int
}
func newSprite(label string, colorClock *colorclock.ColorClock) *sprite {
@ -55,7 +59,8 @@ func newSprite(label string, colorClock *colorclock.ColorClock) *sprite {
// the direction of count and max is important - don't monkey with it
// the value is used in Pixel*() functions to determine which pixel to check
sp.drawSigMax = 8
sp.drawSigCount = sp.drawSigMax + 1
sp.drawSigOff = sp.drawSigMax + 1
sp.drawSigCount = sp.drawSigOff
return sp
}
@ -120,7 +125,7 @@ func (sp *sprite) startDrawing() {
// stopDrawing is used to stop the draw signal prematurely
func (sp *sprite) stopDrawing() {
sp.drawSigCount = sp.drawSigMax + 1
sp.drawSigCount = sp.drawSigOff
}
func (sp *sprite) isDrawing() bool {

View file

@ -25,8 +25,6 @@ type Video struct {
hmm0 uint8
hmm1 uint8
hmbl uint8
// TODO: player/missile number & spacing; trigger lists
}
// New is the preferred method of initialisation for the Video structure
@ -115,10 +113,10 @@ func (vd *Video) TickSpritesForHMOVE(count int) {
func (vd Video) GetPixel() uint8 {
if vd.Playfield.priority {
// priority 1
if use, c := vd.Player0.pixel(); use {
if use, c := vd.Playfield.pixel(); use {
return c
}
if use, c := vd.Missile0.pixel(); use {
if use, c := vd.Ball.pixel(); use {
return c
}
@ -131,19 +129,18 @@ func (vd Video) GetPixel() uint8 {
}
// priority 3
if use, c := vd.Playfield.pixel(); use {
if use, c := vd.Player0.pixel(); use {
return c
}
if use, c := vd.Ball.pixel(); use {
if use, c := vd.Missile0.pixel(); use {
return c
}
} else {
// priority 1
if use, c := vd.Playfield.pixel(); use {
if use, c := vd.Player0.pixel(); use {
return c
}
if use, c := vd.Ball.pixel(); use {
if use, c := vd.Missile0.pixel(); use {
return c
}
@ -156,10 +153,10 @@ func (vd Video) GetPixel() uint8 {
}
// priority 3
if use, c := vd.Player0.pixel(); use {
if use, c := vd.Playfield.pixel(); use {
return c
}
if use, c := vd.Missile0.pixel(); use {
if use, c := vd.Ball.pixel(); use {
return c
}
}
@ -168,6 +165,25 @@ func (vd Video) GetPixel() uint8 {
return vd.Playfield.backgroundColor
}
func createTriggerList(playerSize uint8) []int {
var triggerList []int
switch playerSize {
case 0x0, 0x05, 0x07:
// empty trigger list
case 0x01:
triggerList = []int{4} // 111100
case 0x02:
triggerList = []int{8} // 110111
case 0x03:
triggerList = []int{4, 8} // 111100, 110111
case 0x04:
triggerList = []int{4} // 110111
case 0x06:
triggerList = []int{8, 16} // 110111, 011100
}
return triggerList
}
// ReadVideoMemory checks the TIA memory for changes to registers that are
// interesting to the video sub-system. all changes happen immediately except
// for those where a "schedule" function is called.
@ -175,10 +191,14 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool {
switch register {
case "NUSIZ0":
vd.Missile0.size = (value & 0x30) >> 4
// TODO: player width & trigger lists
vd.Player0.size = value & 0x07
vd.Player0.triggerList = createTriggerList(vd.Player0.size)
vd.Missile0.triggerList = vd.Player0.triggerList
case "NUSIZ1":
vd.Missile1.size = (value & 0x30) >> 4
// TODO: player width & trigger lists
vd.Player1.size = value & 0x07
vd.Player1.triggerList = createTriggerList(vd.Player1.size)
vd.Missile1.triggerList = vd.Player1.triggerList
case "COLUP0":
vd.Player0.color = value & 0xfe
vd.Missile0.color = value & 0xfe
@ -196,9 +216,9 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool {
vd.Playfield.scoremode = value&0x02 == 0x02
vd.Playfield.priority = value&0x04 == 0x04
case "REFP0":
vd.Player0.reflection = value&0x40 == 0x40
vd.Player0.reflected = value&0x04 == 0x04
case "REFP1":
vd.Player1.reflection = value&0x40 == 0x40
vd.Player1.reflected = value&0x04 == 0x04
case "PF0":
vd.Playfield.scheduleWrite(0, value)
case "PF1":

View file

@ -92,28 +92,23 @@ func (vcs *VCS) Step(videoCycleCallback func(*cpu.InstructionResult) error) (int
// TODO: not sure when in the video cycle sequence it should be run
// TODO: is this something that can drift, thereby causing subtly different
// results / graphical effects? is this what RSYNC is for?
vcs.RIOT.ReadRIOTMemory()
vcs.RIOT.Step()
// three color clocks per CPU cycle so we run video cycle three times
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
if vcs.MC.RdyFlg {
videoCycleCallback(r)
}
videoCycleCallback(r)
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
if vcs.MC.RdyFlg {
videoCycleCallback(r)
}
videoCycleCallback(r)
// check for side effects from the CPU operation
vcs.TIA.ReadTIAMemory()
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
if vcs.MC.RdyFlg {
videoCycleCallback(r)
}
videoCycleCallback(r)
}
// TODO: full controller support -- this is emulating the rest state for the