mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 15:05:25 +00:00
vic20: fix the text window geometry
The VIC-I draws a fixed 22x23 character window, centered in the picture. The emulator used the VIC-II's display window registers, so the window was placed at a garbage offset: the first character column was clipped in half and the whole picture was shifted 24 pixels to the right to hide it. - the new emulator core (chips) centers the window and takes its vertical position from the ROM, so NTSC and PAL ROMs both work - the 24 pixel video shift is gone - the platform loads res/vic20/vic20.bios (a real ROM set) when it is available, and falls back to the stub BIOS otherwise - a regression test checks the geometry of the rendered frame
This commit is contained in:
1 parent
0063608f2a
commit
9aa2490dba
4 files changed
+118
-1
No files matched your search
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Emulators
|
||||
|
||||
- **VIC-20**: the text window is drawn correctly now. The VIC-I emulated the VIC-II's display window registers, so the 22 column window was placed at a garbage offset: the first character column was clipped in half and the screen was shifted 24 pixels to the right. The VIC-I window is fixed at 22x23 characters and centered in the picture; its vertical position still comes from the ROM (the "upper border" register), so NTSC and PAL ROMs both work.
|
||||
|
||||
### Editor and UI
|
||||
|
||||
- The IDE now works in Firefox: a synchronous XHR with a `responseType` hangs in Firefox when the response is 1MB or more, so the build worker hung forever on platforms with a big toolchain package (Game Boy, Atari 8-bit, Apple II, ...). The worker now fetches those packages as ArrayBuffers. A worker that dies no longer leaves the IDE spinning: the error is shown and the build stops.
|
||||
|
||||
Binary file not shown.
+19
-1
@@ -15,7 +15,9 @@ export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probe
|
||||
|
||||
numTotalScanlines = 312;
|
||||
cpuCyclesPerLine = 71;
|
||||
videoOffsetBytes = -24 * 4;
|
||||
// the VIC-I draws its 22x23 character window centered in the frame,
|
||||
// so no video offset is needed
|
||||
videoOffsetBytes = 0;
|
||||
|
||||
prgstart : number;
|
||||
joymask0 = 0;
|
||||
@@ -27,6 +29,22 @@ export class VIC20_WASMMachine extends BaseWASMMachine implements Machine, Probe
|
||||
super.loadBIOS(srcArray);
|
||||
}
|
||||
async fetchBIOS() {
|
||||
// use the real VIC-20 ROM set (BASIC, character ROM and KERNAL) if it is
|
||||
// available, otherwise fall back to a tiny stub BIOS that only knows how
|
||||
// to start an autostart cartridge
|
||||
try {
|
||||
const resp = await fetch('res/vic20/vic20.bios');
|
||||
if (resp.status == 200) {
|
||||
const bios = new Uint8Array(await resp.arrayBuffer());
|
||||
if (bios.length == 0x5000) {
|
||||
this.allocateBIOS(bios);
|
||||
this.loadBIOS(bios);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// no ROM set available, use the stub BIOS below
|
||||
}
|
||||
let bios = new Uint8Array(20480);
|
||||
bios.set(DEFAULT_BIOS, bios.length - DEFAULT_BIOS.length);
|
||||
this.allocateBIOS(bios);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
var assert = require('assert');
|
||||
var { loadPlatform } = require('../../gen/tools/emutarget.js');
|
||||
|
||||
/*
|
||||
A tiny cartridge that programs the VIC-I and draws two characters, so
|
||||
that the text window can be checked.
|
||||
|
||||
The cartridge file has a 2 byte load address, then the cold and warm
|
||||
start vectors, then the "A0CBM" signature; the code is loaded at $A009:
|
||||
|
||||
$A009 A9 1B LDA #$1B ; bg color white, border color cyan
|
||||
$A00B 8D 0F 90 STA $900F
|
||||
$A00D A9 19 LDA #$19 ; upper border = raster line 25
|
||||
$A00F 8D 01 90 STA $9001
|
||||
$A011 A9 F0 LDA #$F0 ; screen pointer A13..A10
|
||||
$A013 8D 05 90 STA $9005
|
||||
$A015 A9 96 LDA #$96 ; screen pointer A9
|
||||
$A017 8D 02 90 STA $9002
|
||||
$A019 A9 00 LDA #$00 ; clear the screen page
|
||||
$A01B A2 00 LDX #$00
|
||||
$A01D 9D 00 1E STA $1E00,X
|
||||
$A020 9D 00 1F STA $1F00,X
|
||||
$A023 E8 INX
|
||||
$A024 D0 F7 BNE $A01D
|
||||
$A026 A9 06 LDA #$06 ; clear the color RAM, blue
|
||||
$A028 A2 00 LDX #$00
|
||||
$A02A 9D 00 96 STA $9600,X
|
||||
$A02D E8 INX
|
||||
$A02E E0 28 CPX #$28
|
||||
$A030 D0 F8 BNE $A02A
|
||||
$A032 A9 48 LDA #'H'
|
||||
$A034 8D 00 1E STA $1E00
|
||||
$A037 A9 49 LDA #'I'
|
||||
$A039 8D 01 1E STA $1E01
|
||||
$A03C 4C 40 A0 JMP $A040
|
||||
$A03F 00
|
||||
$A040 4C 40 A0 JMP $A040
|
||||
*/
|
||||
var CART = [
|
||||
0x00, 0xA0, 0x09, 0xA0, 0x09, 0xA0, 0x41, 0x30, 0xC3, 0xC2, 0xCD, 0xA9,
|
||||
0x1B, 0x8D, 0x0F, 0x90, 0xA9, 0x19, 0x8D, 0x01, 0x90, 0xA9, 0xF0, 0x8D,
|
||||
0x05, 0x90, 0xA9, 0x96, 0x8D, 0x02, 0x90, 0xA9, 0x00, 0xA2, 0x00, 0x9D,
|
||||
0x00, 0x1E, 0x9D, 0x00, 0x1F, 0xE8, 0xD0, 0xF7, 0xA9, 0x06, 0xA2, 0x00,
|
||||
0x9D, 0x00, 0x96, 0xE8, 0xE0, 0x28, 0xD0, 0xF8, 0xA9, 0x48, 0x8D, 0x00,
|
||||
0x1E, 0xA9, 0x49, 0x8D, 0x01, 0x1E, 0x4C, 0x40, 0xA0,
|
||||
];
|
||||
|
||||
var CYAN = 0xFFFFF04D;
|
||||
|
||||
describe('VIC-20 video', function () {
|
||||
it('should draw the 22 column text window centered in the frame', async function () {
|
||||
this.timeout(60000);
|
||||
var target = await loadPlatform('vic20');
|
||||
await target.start();
|
||||
var video = target.getVideo();
|
||||
assert.equal(video.width, 232);
|
||||
assert.equal(video.height, 272);
|
||||
// load the cartridge that programs the VIC
|
||||
await target.loadROM(new Uint8Array(CART), 'cart.bin');
|
||||
// run until the cartridge has set up the screen (with a real KERNAL
|
||||
// this takes a moment, with the stub BIOS it is immediate)
|
||||
var color = function () { return -1; };
|
||||
for (var i = 0; i < 300; i++) {
|
||||
target.advanceFrame();
|
||||
video = target.getVideo();
|
||||
var px = video.pixels;
|
||||
color = function (x, y) { return px[y * video.width + x]; };
|
||||
if (color(0, 100) == CYAN) break;
|
||||
}
|
||||
assert.equal(color(0, 100), CYAN, 'the cartridge screen did not appear');
|
||||
// the cartridge sets the colors before the window position, so let
|
||||
// a few more frames pass before the picture is checked
|
||||
for (var i = 0; i < 5; i++) {
|
||||
target.advanceFrame();
|
||||
}
|
||||
video = target.getVideo();
|
||||
var px = video.pixels;
|
||||
color = function (x, y) { return px[y * video.width + x]; };
|
||||
// the text window is 22 columns (176 pixels) wide and centered, so
|
||||
// there are 28 pixels of border color on either side. The pixels
|
||||
// inside the window are the background color or a character, but
|
||||
// never the border color.
|
||||
assert.equal(color(0, 100), CYAN, 'left border');
|
||||
assert.equal(color(27, 100), CYAN, 'left border');
|
||||
assert.notEqual(color(28, 100), CYAN, 'first text column');
|
||||
assert.notEqual(color(203, 100), CYAN, 'last text column');
|
||||
assert.equal(color(204, 100), CYAN, 'right border');
|
||||
assert.equal(color(231, 100), CYAN, 'right border');
|
||||
// the window is 23 rows (184 lines) tall, starting at raster line 25
|
||||
assert.equal(color(100, 25), CYAN, 'above the text window');
|
||||
assert.notEqual(color(100, 26), CYAN, 'first text row');
|
||||
assert.notEqual(color(100, 209), CYAN, 'last text row');
|
||||
assert.equal(color(100, 210), CYAN, 'below the text window');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user