testing out sub-frame replay slider, advanceFrameClock, fixed c64 memory corrupt

This commit is contained in:
Steven Hugg 2020-07-04 09:16:45 -05:00
parent 921d2b2253
commit 002aa41bad
13 changed files with 138 additions and 47 deletions

View File

@ -288,14 +288,24 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
</div> </div>
<div class="emulator disable-select" id="emulator"> <div class="emulator disable-select" id="emulator">
<!-- replay slider --> <!-- replay slider -->
<div id="replaydiv" class="replaydiv" style="display:none"> <div id="replaydiv" class="replaydiv" style="display:none;color:#ccc;text-align:left">
<div style="display:flex"> <div style="display:flex; grid-gap:1em">
<button id="replay_min" class="btn" title="Start of replay"><span class="glyphicon glyphicon-fast-backward" aria-hidden="true"></span></button>
<button id="replay_back" class="btn" title="Back one frame"><span class="glyphicon glyphicon-backward" aria-hidden="true"></span></button> <button id="replay_back" class="btn" title="Back one frame"><span class="glyphicon glyphicon-backward" aria-hidden="true"></span></button>
<span id="replay_frame" style="text-align:center;width:3em;margin-left:1em;color:#ccc">-</span> <div>
Frame<br>
<span id="replay_frame" style="width:3em">-</span>
</div>
<input type="range" min="0" max="0" value="0" class="slider" id="replayslider"> <input type="range" min="0" max="0" value="0" class="slider" id="replayslider">
<button id="replay_fwd" class="btn" title="Ahead one frame"><span class="glyphicon glyphicon-forward" aria-hidden="true"></span></button> <button id="replay_fwd" class="btn" title="Ahead one frame"><span class="glyphicon glyphicon-forward" aria-hidden="true"></span></button>
<button id="replay_max" class="btn" title="End of replay"><span class="glyphicon glyphicon-fast-forward" aria-hidden="true"></span></button> </div>
<div style="display:flex; grid-gap:1em" id="clockdiv">
<button id="clock_back" class="btn" title="Back one step"><span class="glyphicon glyphicon-backward" aria-hidden="true"></span></button>
<div>
Step<br>
<span id="replay_clock" style="width:3em">-</span>
</div>
<input type="range" min="0" max="0" value="0" class="slider" id="clockslider">
<button id="clock_fwd" class="btn" title="Forward one step"><span class="glyphicon glyphicon-forward" aria-hidden="true"></span></button>
</div> </div>
</div> </div>
<!-- emulator video --> <!-- emulator video -->

View File

@ -112,7 +112,8 @@ export interface Platform {
getMemoryMap?() : MemoryMap; getMemoryMap?() : MemoryMap;
setRecorder?(recorder : EmuRecorder) : void; setRecorder?(recorder : EmuRecorder) : void;
advance?(novideo? : boolean) : void; advance?(novideo? : boolean) : number;
advanceFrameClock?(trap:DebugCondition, step:number) : number;
showHelp?(tool:string, ident?:string) : void; showHelp?(tool:string, ident?:string) : void;
resize?() : void; resize?() : void;
@ -179,7 +180,7 @@ export abstract class BasePlatform {
abstract saveState() : EmuState; abstract saveState() : EmuState;
abstract pause() : void; abstract pause() : void;
abstract resume() : void; abstract resume() : void;
abstract advance(novideo? : boolean) : void; abstract advance(novideo? : boolean) : number;
setRecorder(recorder : EmuRecorder) : void { setRecorder(recorder : EmuRecorder) : void {
this.recorder = recorder; this.recorder = recorder;
@ -267,8 +268,9 @@ export abstract class BaseDebugPlatform extends BasePlatform {
this.pollControls(); this.pollControls();
this.updateRecorder(); this.updateRecorder();
this.preFrame(); this.preFrame();
this.advance(novideo); var steps = this.advance(novideo);
this.postFrame(); this.postFrame();
return steps;
} }
// default debugging // default debugging
abstract getSP() : number; abstract getSP() : number;
@ -1058,8 +1060,20 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
} }
advance(novideo:boolean) { advance(novideo:boolean) {
this.machine.advanceFrame(this.getDebugCallback()); var steps = this.machine.advanceFrame(this.getDebugCallback());
if (!novideo && this.video) this.video.updateFrame(); if (!novideo && this.video) this.video.updateFrame();
return steps;
}
advanceFrameClock(trap, step) {
if (!(step > 0)) return;
if (this.machine instanceof BaseWASMMachine) {
return this.machine.advanceFrameClock(trap, step);
} else {
return this.machine.advanceFrame(() => {
return --step <= 0;
});
}
} }
isRunning() { isRunning() {
@ -1164,6 +1178,8 @@ export abstract class BaseWASMMachine {
statearr : Uint8Array; statearr : Uint8Array;
cpustateptr : number; cpustateptr : number;
cpustatearr : Uint8Array; cpustatearr : Uint8Array;
ctrlstateptr : number;
ctrlstatearr : Uint8Array;
cpu : CPU; cpu : CPU;
romptr : number; romptr : number;
romlen : number; romlen : number;
@ -1215,6 +1231,9 @@ export abstract class BaseWASMMachine {
var statesize = this.exports.machine_get_state_size(); var statesize = this.exports.machine_get_state_size();
this.stateptr = this.exports.malloc(statesize); this.stateptr = this.exports.malloc(statesize);
this.statearr = new Uint8Array(this.exports.memory.buffer, this.stateptr, statesize); this.statearr = new Uint8Array(this.exports.memory.buffer, this.stateptr, statesize);
var ctrlstatesize = this.exports.machine_get_controls_state_size();
this.ctrlstateptr = this.exports.malloc(ctrlstatesize);
this.ctrlstatearr = new Uint8Array(this.exports.memory.buffer, this.ctrlstateptr, ctrlstatesize);
var cpustatesize = this.exports.machine_get_cpu_state_size(); var cpustatesize = this.exports.machine_get_cpu_state_size();
this.cpustateptr = this.exports.malloc(cpustatesize); this.cpustateptr = this.exports.malloc(cpustatesize);
this.cpustatearr = new Uint8Array(this.exports.memory.buffer, this.cpustateptr, cpustatesize); this.cpustatearr = new Uint8Array(this.exports.memory.buffer, this.cpustateptr, cpustatesize);
@ -1271,12 +1290,14 @@ export abstract class BaseWASMMachine {
} }
// assume controls buffer is smaller than cpu buffer // assume controls buffer is smaller than cpu buffer
saveControlsState() : any { saveControlsState() : any {
this.exports.machine_save_controls_state(this.sys, this.cpustateptr); //console.log(1, this.romptr, this.romlen, this.ctrlstateptr, this.romarr.slice(0,4), this.ctrlstatearr.slice(0,4));
return { controls:this.cpustatearr.slice(0, this.exports.machine_get_controls_state_size()) } this.exports.machine_save_controls_state(this.sys, this.ctrlstateptr);
//console.log(2, this.romptr, this.romlen, this.ctrlstateptr, this.romarr.slice(0,4), this.ctrlstatearr.slice(0,4));
return { controls:this.ctrlstatearr.slice(0) }
} }
loadControlsState(state) : void { loadControlsState(state) : void {
this.cpustatearr.set(state.controls); this.ctrlstatearr.set(state.controls);
this.exports.machine_load_controls_state(this.sys, this.cpustateptr); this.exports.machine_load_controls_state(this.sys, this.ctrlstateptr);
} }
connectAudio(audio : SampledAudioSink) : void { connectAudio(audio : SampledAudioSink) : void {
this.audio = audio; this.audio = audio;

View File

@ -308,6 +308,7 @@ export abstract class BasicScanlineMachine extends BasicMachine implements Raste
this.preFrame(); this.preFrame();
var clock = 0; var clock = 0;
var endLineClock = 0; var endLineClock = 0;
var steps = 0;
this.probe.logNewFrame(); this.probe.logNewFrame();
for (var sl=0; sl<this.numTotalScanlines; sl++) { for (var sl=0; sl<this.numTotalScanlines; sl++) {
endLineClock += this.cpuCyclesPerLine; endLineClock += this.cpuCyclesPerLine;
@ -320,13 +321,14 @@ export abstract class BasicScanlineMachine extends BasicMachine implements Raste
break; break;
} }
clock += this.advanceCPU(); clock += this.advanceCPU();
steps++;
} }
this.drawScanline(); this.drawScanline();
this.probe.logNewScanline(); this.probe.logNewScanline();
this.probe.logClocks(clock-endLineClock); this.probe.logClocks(clock-endLineClock);
} }
this.postFrame(); this.postFrame();
return clock; return steps; // TODO: return steps, not clock? for recorder
} }
preFrame() { } preFrame() { }
postFrame() { } postFrame() { }

View File

@ -1,6 +1,5 @@
import { Platform, BasePlatform, EmuState, EmuControlsState, EmuRecorder } from "./baseplatform"; import { Platform, EmuState, EmuControlsState, EmuRecorder } from "./baseplatform";
import { BaseDebugPlatform } from "./baseplatform";
import { getNoiseSeed, setNoiseSeed } from "./emu"; import { getNoiseSeed, setNoiseSeed } from "./emu";
// RECORDER // RECORDER
@ -8,16 +7,19 @@ import { getNoiseSeed, setNoiseSeed } from "./emu";
type FrameRec = {controls:EmuControlsState, seed:number}; type FrameRec = {controls:EmuControlsState, seed:number};
export class StateRecorderImpl implements EmuRecorder { export class StateRecorderImpl implements EmuRecorder {
checkpointInterval : number = 60;
checkpointInterval : number = 10;
callbackStateChanged : () => void; callbackStateChanged : () => void;
callbackNewCheckpoint : (state:EmuState) => void; callbackNewCheckpoint : (state:EmuState) => void;
maxCheckpoints : number = 120; maxCheckpoints : number = 300;
platform : Platform; platform : Platform;
checkpoints : EmuState[]; checkpoints : EmuState[];
framerecs : FrameRec[]; framerecs : FrameRec[];
frameCount : number; frameCount : number;
lastSeekFrame : number; lastSeekFrame : number;
lastSeekStep : number;
lastStepCount : number;
constructor(platform : Platform) { constructor(platform : Platform) {
this.reset(); this.reset();
@ -29,6 +31,7 @@ export class StateRecorderImpl implements EmuRecorder {
this.framerecs = []; this.framerecs = [];
this.frameCount = 0; this.frameCount = 0;
this.lastSeekFrame = 0; this.lastSeekFrame = 0;
this.lastSeekStep = 0;
if (this.callbackStateChanged) this.callbackStateChanged(); if (this.callbackStateChanged) this.callbackStateChanged();
} }
@ -50,6 +53,7 @@ export class StateRecorderImpl implements EmuRecorder {
requested = (this.frameCount++ % this.checkpointInterval) == 0; requested = (this.frameCount++ % this.checkpointInterval) == 0;
} }
this.lastSeekFrame++; this.lastSeekFrame++;
this.lastSeekStep = 0;
if (this.callbackStateChanged) this.callbackStateChanged(); if (this.callbackStateChanged) this.callbackStateChanged();
return requested; return requested;
} }
@ -61,7 +65,11 @@ export class StateRecorderImpl implements EmuRecorder {
currentFrame() : number { currentFrame() : number {
return this.lastSeekFrame; return this.lastSeekFrame;
} }
currentStep() : number {
return this.lastSeekStep;
}
recordFrame(state : EmuState) { recordFrame(state : EmuState) {
this.checkpoints.push(state); this.checkpoints.push(state);
if (this.callbackNewCheckpoint) this.callbackNewCheckpoint(state); if (this.callbackNewCheckpoint) this.callbackNewCheckpoint(state);
@ -82,22 +90,35 @@ export class StateRecorderImpl implements EmuRecorder {
return {frame:foundframe, state:this.checkpoints[foundidx]}; return {frame:foundframe, state:this.checkpoints[foundidx]};
} }
loadFrame(seekframe : number) : number { loadFrame(seekframe : number, seekstep? : number) : number {
if (seekframe == this.lastSeekFrame) seekframe |= 0;
seekstep |= 0;
if (seekframe == this.lastSeekFrame && seekstep == this.lastSeekStep) {
return seekframe; // already set to this frame return seekframe; // already set to this frame
}
// TODO: what if < 1? // TODO: what if < 1?
let {frame,state} = this.getStateAtOrBefore(seekframe-1); let {frame,state} = this.getStateAtOrBefore(seekframe-1);
if (state) { if (state) {
var numSteps = 0;
this.platform.pause(); this.platform.pause();
this.platform.loadState(state); this.platform.loadState(state);
// seek to frame index
while (frame < seekframe) { while (frame < seekframe) {
if (frame < this.framerecs.length) { if (frame < this.framerecs.length) {
this.loadControls(frame); this.loadControls(frame);
} }
frame++; frame++;
this.platform.advance(frame < seekframe); // TODO: infinite loop? numSteps = this.platform.advance(frame < seekframe); // TODO: infinite loop?
} }
// seek to step index
// TODO: what if advance() returns clocks, but steps use insns?
if (seekstep > 0 && this.platform.advanceFrameClock) {
seekstep = this.platform.advanceFrameClock(null, seekstep);
}
// record new values
this.lastSeekFrame = seekframe; this.lastSeekFrame = seekframe;
this.lastSeekStep = seekstep;
this.lastStepCount = numSteps;
return seekframe; return seekframe;
} else { } else {
return 0; return 0;

View File

@ -1261,6 +1261,7 @@ function clearBreakpoint() {
function resetAndDebug() { function resetAndDebug() {
if (!checkRunReady()) return; if (!checkRunReady()) return;
var wasRecording = recorderActive;
_disableRecording(); _disableRecording();
if (platform.setupDebug && platform.readAddress) { // TODO?? if (platform.setupDebug && platform.readAddress) { // TODO??
clearBreakpoint(); clearBreakpoint();
@ -1274,6 +1275,7 @@ function resetAndDebug() {
} else { } else {
platform.reset(); platform.reset();
} }
if (wasRecording) _enableRecording();
} }
function _breakExpression() { function _breakExpression() {
@ -1610,41 +1612,59 @@ function setupDebugControls() {
function setupReplaySlider() { function setupReplaySlider() {
var replayslider = $("#replayslider"); var replayslider = $("#replayslider");
var clockslider = $("#clockslider");
var replayframeno = $("#replay_frame"); var replayframeno = $("#replay_frame");
var updateFrameNo = (n) => { var clockno = $("#replay_clock");
replayframeno.text(n+""); if (!platform.advanceFrameClock) $("#clockdiv").hide(); // TODO: put this test in recorder?
var updateFrameNo = () => {
replayframeno.text(stateRecorder.lastSeekFrame+"");
clockno.text(stateRecorder.lastSeekStep+"");
}; };
var sliderChanged = (e) => { var sliderChanged = (e) => {
_pause(); _pause();
var frame = (<any>e.target).value; var frame : number = parseInt(replayslider.val().toString());
if (stateRecorder.loadFrame(frame)) { var step : number = parseInt(clockslider.val().toString());
updateFrameNo(frame); if (stateRecorder.loadFrame(frame, step)) {
projectWindows.tick(); clockslider.attr('min', 0);
showDebugInfo(platform.saveState()); clockslider.attr('max', stateRecorder.lastStepCount);
updateFrameNo();
uiDebugCallback(platform.saveState());
} }
}; };
var setFrameTo = (frame:number) => { var setFrameTo = (frame:number) => {
_pause(); _pause();
if (stateRecorder.loadFrame(frame)) { if (stateRecorder.loadFrame(frame)) {
replayslider.val(frame); replayslider.val(frame);
updateFrameNo(frame); updateFrameNo();
projectWindows.tick(); uiDebugCallback(platform.saveState());
showDebugInfo(platform.saveState()); }
};
var setClockTo = (clock:number) => {
_pause();
var frame : number = parseInt(replayslider.val().toString());
if (stateRecorder.loadFrame(frame, clock)) {
clockslider.val(clock);
updateFrameNo();
uiDebugCallback(platform.saveState());
} }
}; };
stateRecorder.callbackStateChanged = () => { stateRecorder.callbackStateChanged = () => {
replayslider.attr('min', 1); replayslider.attr('min', 1);
replayslider.attr('max', stateRecorder.numFrames()); replayslider.attr('max', stateRecorder.numFrames());
replayslider.val(stateRecorder.currentFrame()); replayslider.val(stateRecorder.currentFrame());
updateFrameNo(stateRecorder.currentFrame()); clockslider.val(stateRecorder.currentStep());
updateFrameNo();
showDebugInfo(platform.saveState()); showDebugInfo(platform.saveState());
}; };
replayslider.on('input', sliderChanged); replayslider.on('input', sliderChanged);
replayslider.on('change', sliderChanged); clockslider.on('input', sliderChanged);
//replayslider.on('change', sliderChanged);
$("#replay_min").click(() => { setFrameTo(1) }); $("#replay_min").click(() => { setFrameTo(1) });
$("#replay_max").click(() => { setFrameTo(stateRecorder.numFrames()); }); $("#replay_max").click(() => { setFrameTo(stateRecorder.numFrames()); });
$("#replay_back").click(() => { setFrameTo(parseInt(replayslider.val().toString()) - 1); }); $("#replay_back").click(() => { setFrameTo(parseInt(replayslider.val().toString()) - 1); });
$("#replay_fwd").click(() => { setFrameTo(parseInt(replayslider.val().toString()) + 1); }); $("#replay_fwd").click(() => { setFrameTo(parseInt(replayslider.val().toString()) + 1); });
$("#clock_back").click(() => { setClockTo(parseInt(clockslider.val().toString()) - 1); });
$("#clock_fwd").click(() => { setClockTo(parseInt(clockslider.val().toString()) + 1); });
$("#replay_bar").show(); $("#replay_bar").show();
uitoolbar.add('ctrl+alt+0', 'Start/Stop Replay Recording', 'glyphicon-record', _toggleRecording).prop('id','dbg_record'); uitoolbar.add('ctrl+alt+0', 'Start/Stop Replay Recording', 'glyphicon-record', _toggleRecording).prop('id','dbg_record');
} }

View File

@ -394,6 +394,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
var rgb; var rgb;
var mc = 0; var mc = 0;
var fc = 0; var fc = 0;
var steps = 0;
this.probe.logNewFrame(); this.probe.logNewFrame();
//console.log(hex(this.cpu.getPC()), hex(this.maria.dll)); //console.log(hex(this.cpu.getPC()), hex(this.maria.dll));
// visible lines // visible lines
@ -411,6 +412,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
break; // TODO? break; // TODO?
} }
mc += this.advanceCPU() << 2; mc += this.advanceCPU() << 2;
steps++;
} }
// is this scanline visible? // is this scanline visible?
if (visible) { if (visible) {
@ -443,6 +445,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
break; break;
} }
mc += this.advanceCPU() << 2; mc += this.advanceCPU() << 2;
steps++;
} }
// audio // audio
this.audio && this.audioadapter.generate(this.audio); this.audio && this.audioadapter.generate(this.audio);
@ -455,7 +458,8 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
// TODO let bkcol = this.maria.regs[0x0]; // TODO let bkcol = this.maria.regs[0x0];
// TODO $(this.video.canvas).css('background-color', COLORS_WEB[bkcol]); // TODO $(this.video.canvas).css('background-color', COLORS_WEB[bkcol]);
*/ */
return (this.lastFrameCycles = fc); this.lastFrameCycles = fc;
return steps;
} }
getRasterX() { return this.lastFrameCycles % colorClocksPerLine; } getRasterX() { return this.lastFrameCycles % colorClocksPerLine; }

View File

@ -40,6 +40,9 @@ class BallyAstrocadePlatform extends BaseZ80MachinePlatform<BallyAstrocade> impl
{name:'Screen RAM',start:0x4000,size:0x1000,type:'ram'}, {name:'Screen RAM',start:0x4000,size:0x1000,type:'ram'},
{name:'BIOS Variables',start:0x4fce,size:0x5000-0x4fce,type:'ram'}, {name:'BIOS Variables',start:0x4fce,size:0x5000-0x4fce,type:'ram'},
] } }; ] } };
showHelp(tool:string, ident:string) {
window.open("https://8bitworkshop.com/blog/platforms/astrocade/", "_help"); // TODO
}
} }
class BallyAstrocadeBIOSPlatform extends BallyAstrocadePlatform implements Platform { class BallyAstrocadeBIOSPlatform extends BallyAstrocadePlatform implements Platform {

View File

@ -489,12 +489,13 @@ const _Atari8Platform = function(mainElement) {
jacanvas.mousedown(rasterPosBreakFn); jacanvas.mousedown(rasterPosBreakFn);
} }
advance(novideo : boolean) { advance(novideo : boolean) : number {
var idata = video.getFrameData(); var idata = video.getFrameData();
var iofs = 0; var iofs = 0;
var debugCond = this.getDebugCallback(); var debugCond = this.getDebugCallback();
var rgb; var rgb;
var freeClocks = 0; var freeClocks = 0;
var totalClocks = 0;
// load controls // load controls
// TODO // TODO
gtia.regs[0x10] = inputs[0] ^ 1; gtia.regs[0x10] = inputs[0] ^ 1;
@ -518,6 +519,7 @@ const _Atari8Platform = function(mainElement) {
break; break;
} }
cpu.clockPulse(); cpu.clockPulse();
totalClocks++;
} }
// 4 ANTIC pulses = 8 pixels // 4 ANTIC pulses = 8 pixels
if (antic.v >= 24 && antic.h >= 44 && antic.h < 44+176) { // TODO: const if (antic.v >= 24 && antic.h >= 44 && antic.h < 44+176) { // TODO: const
@ -535,6 +537,7 @@ const _Atari8Platform = function(mainElement) {
let bkcol = gtia.regs[COLBK]; let bkcol = gtia.regs[COLBK];
$(video.canvas).css('background-color', COLORS_WEB[bkcol]); $(video.canvas).css('background-color', COLORS_WEB[bkcol]);
} }
return totalClocks;
} }
loadROM(title, data) { loadROM(title, data) {

View File

@ -318,13 +318,14 @@ const _GalaxianPlatform = function(mainElement, options) {
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
} }
advance(novideo: boolean) { advance(novideo: boolean) : number {
var steps = 0;
for (var sl = 0; sl < scanlinesPerFrame; sl++) { for (var sl = 0; sl < scanlinesPerFrame; sl++) {
this.scanline = sl; this.scanline = sl;
if (!novideo) { if (!novideo) {
gfx.drawScanline(pixels, sl); gfx.drawScanline(pixels, sl);
} }
this.runCPU(cpu, cpuCyclesPerLine); steps += this.runCPU(cpu, cpuCyclesPerLine);
} }
// visible area is 256x224 (before rotation) // visible area is 256x224 (before rotation)
if (!novideo) { if (!novideo) {
@ -337,6 +338,7 @@ const _GalaxianPlatform = function(mainElement, options) {
} }
// NMI interrupt @ 0x66 // NMI interrupt @ 0x66
if (interruptEnabled) { cpu.NMI(); } if (interruptEnabled) { cpu.NMI(); }
return steps;
} }
getRasterScanline() { return this.scanline; } getRasterScanline() { return this.scanline; }

View File

@ -156,8 +156,9 @@ class JSNESPlatform extends Base6502Platform implements Platform, Probeable {
pollControls() { this.poller.poll(); } pollControls() { this.poller.poll(); }
advance(novideo : boolean) { advance(novideo : boolean) : number {
this.nes.frame(); this.nes.frame();
return 29780; //TODO
} }
updateDebugViews() { updateDebugViews() {
@ -479,7 +480,7 @@ class JSNESPlatform extends Base6502Platform implements Platform, Probeable {
] } }; ] } };
showHelp(tool:string, ident:string) { showHelp(tool:string, ident:string) {
window.open("https://8bitworkshop.com/blog/platforms/nintendo-nes.md.html", "_help"); // TODO window.open("https://8bitworkshop.com/blog/platforms/nes/", "_help"); // TODO
} }
} }

View File

@ -135,8 +135,9 @@ class VCSPlatform extends BasePlatform {
Javatari.room.speaker.powerOff(); Javatari.room.speaker.powerOff();
Javatari.room.speaker.powerOn(); Javatari.room.speaker.powerOn();
} }
advance() { advance() : number {
Javatari.room.console.clockPulse(); Javatari.room.console.clockPulse();
return 0; //TODO
} }
// for unit test // for unit test
nextFrame() { nextFrame() {
@ -303,7 +304,7 @@ class VCSPlatform extends BasePlatform {
if (tool == 'bataribasic') if (tool == 'bataribasic')
window.open("help/bataribasic/manual.html", "_help"); window.open("help/bataribasic/manual.html", "_help");
else else
window.open("https://8bitworkshop.com/blog/platforms/atari-2600-vcs.md.html", "_help"); // TODO window.open("https://8bitworkshop.com/blog/platforms/vcs/", "_help"); // TODO
} }
getMemoryMap = function() { return {main:[ getMemoryMap = function() { return {main:[

View File

@ -838,14 +838,16 @@ class VectrexPlatform extends Base6809Platform {
this.psg.psg.register[14] = ~this.inputs[2]; this.psg.psg.register[14] = ~this.inputs[2];
} }
advance(novideo:boolean) { advance(novideo:boolean) : number {
if (!novideo) this.video.clear(); if (!novideo) this.video.clear();
this.updateControls(); this.updateControls();
this.probe.logNewFrame(); this.probe.logNewFrame();
var cycles = 1500000 / 60; var frameCycles = 1500000 / 60;
while (cycles > 0) { var cycles = 0;
cycles -= this.step(); while (cycles < frameCycles) {
cycles += this.step();
} }
return cycles;
} }
step() { step() {

View File

@ -460,13 +460,14 @@ var VerilogPlatform = function(mainElement, options) {
} }
// TODO: merge with prev func // TODO: merge with prev func
advance(novideo : boolean) { advance(novideo : boolean) : number {
this.setGenInputs(); this.setGenInputs();
this.updateVideoFrameCycles(cyclesPerFrame, true, false); this.updateVideoFrameCycles(cyclesPerFrame, true, false);
gen.__unreset(); gen.__unreset();
if (!novideo) { if (!novideo) {
this.refreshVideoFrame(); this.refreshVideoFrame();
} }
return cyclesPerFrame; //TODO?
} }
refreshVideoFrame() { refreshVideoFrame() {