Woz protections automatic tests
parent
cde673a552
commit
fe15ce8c93
@ -0,0 +1,116 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testWoz(t *testing.T, sequencer bool, file string, expectedTracks []int, cycleLimit uint64) {
|
||||
at := makeApple2Tester("2enh")
|
||||
tt := makeTrackTracerSummary()
|
||||
|
||||
var err error
|
||||
if sequencer {
|
||||
err = at.a.AddDisk2Sequencer(6, "woz_test_images/"+file, "", tt)
|
||||
} else {
|
||||
err = at.a.AddDisk2(6, "woz_test_images/"+file, "", tt)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
expectedLen := len(expectedTracks)
|
||||
|
||||
at.terminateCondition = func(a *Apple2) bool {
|
||||
tracksMayMatch := len(tt.quarterTracks) >= expectedLen &&
|
||||
tt.quarterTracks[expectedLen-1] == expectedTracks[expectedLen-1]
|
||||
|
||||
return tracksMayMatch || a.cpu.GetCycles() > cycleLimit
|
||||
}
|
||||
at.run()
|
||||
|
||||
if !tt.isTraceAsExpected(expectedTracks) {
|
||||
t.Errorf("Quarter tracks, expected %#v, got %#v", expectedTracks, tt.quarterTracks)
|
||||
}
|
||||
|
||||
//t.Errorf("Cycles: %d vs %d", at.a.cpu.GetCycles(), cycleLimit)
|
||||
}
|
||||
|
||||
const (
|
||||
all = 0
|
||||
seq = 1 // Passes only with the sequencer implementation
|
||||
none = 2 // Fails also with the sequencer implementation
|
||||
)
|
||||
|
||||
func TestWoz(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
skip int
|
||||
disk string
|
||||
cycleLimit uint64
|
||||
expectedTracks []int
|
||||
}{
|
||||
// How to being
|
||||
// DOS 3.2, requires 13 sector disks
|
||||
{"DOS3.3", all, "DOS 3.3 System Master.woz", 11_000_000, []int{0, 8, 0, 76, 68, 84, 68, 84, 68, 92, 16, 24}},
|
||||
|
||||
// Next choices
|
||||
{"Bouncing Kamungas", all, "Bouncing Kamungas - Disk 1, Side A.woz", 30_000_000, []int{0, 32, 0, 40, 0}},
|
||||
{"Commando", seq, "Commando - Disk 1, Side A.woz", 14_000_000, []int{0, 136, 68, 128, 68, 128, 68, 124, 12, 116, 108}},
|
||||
{"Planetfall", all, "Planetfall - Disk 1, Side A.woz", 4_000_000, []int{0, 8}},
|
||||
{"Rescue Raiders", all, "Rescue Raiders - Disk 1, Side B.woz", 80_000_000, []int{
|
||||
0, 84, 44, 46,
|
||||
0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
|
||||
84, 44, 116, 4, 8, 4, 12, 8, 84, 44, 132, 0, 120, 44, 84, 44, 124, 0, 120, 44}},
|
||||
{"Sammy Lightfoot", all, "Sammy Lightfoot - Disk 1, Side A.woz", 80_000_000, []int{0, 64, 8, 20}},
|
||||
{"Stargate", all, "Stargate - Disk 1, Side A.woz", 50_000_000, []int{0, 8, 0, 72, 68, 80, 68, 80, 12, 44}},
|
||||
|
||||
// Cross track sync
|
||||
{"Blazing Paddles", all, "Blazing Paddles (Baudville).woz", 6_000_000, []int{0, 28, 0, 16, 12, 56, 52, 80}},
|
||||
{"Take 1", all, "Take 1 (Baudville).woz", 8_000_000, []int{0, 28, 0, 4, 0, 72, 0, 20, 0}},
|
||||
{"Hard Hat Mack", all, "Hard Hat Mack - Disk 1, Side A.woz", 10_000_000, []int{0, 134, 132}},
|
||||
|
||||
// Half tracks
|
||||
{"The Bilestoad", all, "The Bilestoad - Disk 1, Side A.woz", 6_000_000, []int{0, 24}},
|
||||
|
||||
// Even more bit fiddling
|
||||
{"Dino Eggs", all, "Dino Eggs - Disk 1, Side A.woz", 9_000_000, []int{0, 78, 60, 108, 32}},
|
||||
{"Crisis Mountain", all, "Crisis Mountain - Disk 1, Side A.woz", 20_000_000, []int{0, 32, 8, 32, 20, 76, 20, 36, 32, 84, 52, 64}},
|
||||
{"Miner 2049er II", all, "Miner 2049er II - Disk 1, Side A.woz", 11_000_000, []int{0, 12, 8, 32, 12, 136, 132}},
|
||||
|
||||
// When bits aren't really bits
|
||||
{"The Print Shop Companion", all, "The Print Shop Companion - Disk 1, Side A.woz", 14_000_000, []int{0, 68, 44, 68, 40, 68, 40, 136, 60}},
|
||||
|
||||
// What is the lifepsan of the data latch?
|
||||
{"First Math Adventures", seq, "First Math Adventures - Understanding Word Problems.woz", 6_000_000, []int{0, 8, 0, 68, 12, 20}},
|
||||
|
||||
// Reading Offset Data Streams
|
||||
{"Wings of Fury", seq, "Wings of Fury - Disk 1, Side A.woz", 410_000_000, []int{0, 4, 0, 136, 124, 128, 24, 136, 124, 128, 24, 136, 124, 128, 24, 136, 124, 128, 24, 104}},
|
||||
{"Stickybear Town Builder", all, "Stickybear Town Builder - Disk 1, Side A.woz", 8_000_000, []int{0, 16, 12, 112, 80, 100, 8}},
|
||||
|
||||
// Optimal bit timing
|
||||
// Requires disk change {"Border Zone", "Border Zone - Disk 1, Side A.woz", 500_000_000, []int{1,1,1,1,1,1}},
|
||||
|
||||
// Extra
|
||||
{"Mr. Do", seq, "Mr. Do.woz", 95_000_000, []int{0, 108, 48, 104, 72, 84, 0, 4}},
|
||||
{"Wavy Navy", all, "Wavy Navy.woz", 9_000_000, []int{0, 136}},
|
||||
// SAGA6 requires disk change,
|
||||
// Note that Congo Bongo works with the non sequencer implementation but the test is unstable
|
||||
{"Congo Bongo", seq, "Congo Bongo.woz", 8_000_000, []int{0, 4, 2, 40, 20, 40, 16, 124, 116}},
|
||||
// Wizardry III requires disk change,
|
||||
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if tc.skip == all {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
testWoz(t, false, tc.disk, tc.expectedTracks, tc.cycleLimit)
|
||||
})
|
||||
}
|
||||
if tc.skip == all || tc.skip == seq {
|
||||
t.Run(tc.name+" SEQ", func(t *testing.T) {
|
||||
testWoz(t, true, tc.disk, tc.expectedTracks, tc.cycleLimit)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package izapple2
|
||||
|
||||
type trackTracer interface {
|
||||
traceTrack(quarterTrack int)
|
||||
}
|
||||
|
||||
type trackTracerSummary struct {
|
||||
quarterTracks []int
|
||||
}
|
||||
|
||||
func makeTrackTracerSummary() *trackTracerSummary {
|
||||
var tt trackTracerSummary
|
||||
tt.quarterTracks = make([]int, 0, 100)
|
||||
return &tt
|
||||
}
|
||||
|
||||
func (tt *trackTracerSummary) traceTrack(quarterTrack int) {
|
||||
if tt == nil {
|
||||
return
|
||||
}
|
||||
|
||||
length := len(tt.quarterTracks)
|
||||
if length == 0 {
|
||||
// Second change, just record
|
||||
tt.quarterTracks = append(tt.quarterTracks, quarterTrack)
|
||||
return
|
||||
}
|
||||
|
||||
last := tt.quarterTracks[length-1]
|
||||
if quarterTrack == last {
|
||||
// No changes
|
||||
return
|
||||
}
|
||||
|
||||
if length == 1 {
|
||||
// Second change, just record
|
||||
tt.quarterTracks = append(tt.quarterTracks, quarterTrack)
|
||||
return
|
||||
}
|
||||
|
||||
// We don't want to registers the initial jumps around 0 seen when initializing the disk to track 0
|
||||
prevToLast := tt.quarterTracks[length-2]
|
||||
if length == 2 && prevToLast == 0 && (last == 1 || last == 2) && quarterTrack == 0 {
|
||||
tt.quarterTracks = tt.quarterTracks[0:0]
|
||||
}
|
||||
|
||||
// We don't want to track each increment. If tracks goes from 1 to 14, we just want 1 and 14.
|
||||
wasGoingUp := last > prevToLast
|
||||
isGoingUp := quarterTrack > last
|
||||
if isGoingUp == wasGoingUp {
|
||||
// Same direction, update the last registry
|
||||
tt.quarterTracks[length-1] = quarterTrack
|
||||
} else {
|
||||
// Change direction, add a new registry
|
||||
tt.quarterTracks = append(tt.quarterTracks, quarterTrack)
|
||||
}
|
||||
}
|
||||
|
||||
func (tt *trackTracerSummary) isTraceAsExpected(expected []int) bool {
|
||||
if len(tt.quarterTracks) != len(expected) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range tt.quarterTracks {
|
||||
if v != expected[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,21 @@
|
||||
[From Apple II Slack, 4am (2021-06-29)]
|
||||
|
||||
On the subject of "tricky woz images beyond the official test suite," I would recommend
|
||||
|
||||
https://archive.org/details/wozaday_Mr_Do (success = boot to joystick calibration)
|
||||
https://archive.org/details/wozaday_Wavy_Navy (success = boot to game)
|
||||
https://archive.org/details/wozaday_SAGA_6_Strange_Odyssey (success = begin game, climb down ladder, take shovel)
|
||||
https://archive.org/details/wozaday_CongoBongo (success = boot to game)
|
||||
https://archive.org/details/wozaday_Wizardry_III (success = boot to title screen, press a key, boot to main menu)
|
||||
|
||||
|
||||
|
||||
Mr. Do relies on weakbits on track 0. Also, the protection check uses LDA $C088,X to fetch nibbles and expects the entire check to complete before the drive motor turns off.
|
||||
|
||||
Wavy Navy relies on weakbits on track $22
|
||||
|
||||
Strange Odyssey relies on both track length and weakbits on track $22
|
||||
|
||||
Congo Bongo relies on weakbits on track 1, to a much greater degree than others. There is only one small sequence of valid nibbles, surrounded by an entire track of weakbits. This has flummoxed many emulators.
|
||||
|
||||
Wizardry III relies on track length, and it is extremely sensitive to the precise timing of when the data latch "sees" bits and how long it holds on to a full nibble before resetting itself.
|
Loading…
Reference in New Issue