Files

1008 lines
34 KiB
C++

#include <string>
#include <vector>
#include "doctest.h"
#include "../source/vm.h"
#include "../source/hostVmShared.h"
#include "../source/host.h"
#include "stubhost.h"
#include "../source/fontdata.h"
TEST_CASE("Vm memory functions") {
StubHost* stubHost = new StubHost();
PicoRam* memory = new PicoRam();
memory->Reset();
Graphics* graphics = new Graphics(get_font_data(), memory);
Input* input = new Input(memory);
Audio* audio = new Audio(memory);
Vm* vm = new Vm(stubHost, memory, graphics, input, audio);
SUBCASE("memory data stats with 0"){
CHECK(memory->data[0] == 0);
}
SUBCASE("resetting memory zeroes out everything except general use, color bitmask, screen data mapping, and map width"){
for(int i = 0; i < 0x10000; ++i) {
memory->data[i] = i & 255;
}
memory->Reset();
bool correctValues = true;
for(int i = 0; i < 0x8000; ++i) {
if (i == 0x5f5e) {
correctValues &= memory->data[i] == 255;
}
else if (i == 0x5f55) {
correctValues &= memory->data[i] == 0x60;
}
else if (i == 0x5f56) {
correctValues &= memory->data[i] == 0x20;
}
else if (i == 0x5f57) {
correctValues &= memory->data[i] == 128;
}
else if (i < 0x4300 || i > 0x5eff) {
correctValues &= memory->data[i] == 0;
}
}
CHECK(correctValues);
}
SUBCASE("size of PicoRam struct corrct"){
CHECK(sizeof(PicoRam) == 0x10000);
}
SUBCASE("size of data correct"){
CHECK(sizeof(memory->data) == 0x10000);
}
SUBCASE("simple peek and poke"){
vm->vm_poke(2, 232);
CHECK(vm->vm_peek(2) == 232);
}
SUBCASE("simple peek2 and poke2"){
vm->vm_poke2(41, -1031);
CHECK(vm->vm_peek2(41) == -1031);
}
SUBCASE("simple peek4 and poke4"){
vm->vm_poke4(0x7123, 3584.0059);
CHECK(vm->vm_peek4(0x7123).bits() == ((z8::fix32)3584.0059).bits());
}
SUBCASE("poking spritesheet"){
//147: 1001 0011
//left pixel: 0011 = 3
//right pixel: 1001 = 9
vm->vm_poke(0, 147);
CHECK_EQ(graphics->sget(0, 0), 3);
CHECK_EQ(graphics->sget(1, 0), 9);
}
SUBCASE("poking map"){
vm->vm_poke(0x2000, 201);
vm->vm_poke(0x2003, 11);
CHECK_EQ(graphics->mget(0, 0), 201);
CHECK_EQ(graphics->mget(3, 0), 11);
}
SUBCASE("poking sprite flags"){
//39: 0010 0111
vm->vm_poke(0x3000, 39);
CHECK_EQ(graphics->fget(0), 39);
CHECK_EQ(graphics->fget(0, 0), true);
CHECK_EQ(graphics->fget(0, 3), false);
}
SUBCASE("poking music"){
//78: 0100 1110
vm->vm_poke(0x3100, 78);
CHECK_EQ(memory->songs[0].getSfx0(), 78);
CHECK_EQ(memory->songs[0].getStart(), 0);
}
SUBCASE("poking sfx"){
//97: 0110 0001
//key is first 6 bits: 10 0001 : 33
//waveform is next 3: (0) 01 : 1
vm->vm_poke(0x3200, 97);
CHECK_EQ(memory->sfx[0].notes[0].getKey(), 33);
CHECK_EQ(memory->sfx[0].notes[0].getWaveform(), 1);
}
SUBCASE("poking general use ram"){
vm->vm_poke(0x4300, 215);
CHECK_EQ(memory->generalUseRam[0], 215);
}
SUBCASE("setting cart data"){
vm->vm_cartdata("dummy");
vm->vm_dset(13, 56);
CHECK_EQ(vm->vm_dget(13), (fix32)56);
}
SUBCASE("poking cart data"){
//need to poke after cartdata is called to set the current key, otherwise it will get wiped
vm->vm_cartdata("dummy");
vm->vm_poke(0x5e02, 56);
CHECK_EQ(memory->cartData[2], 56);
CHECK_EQ(vm->vm_dget(0), (fix32)56);
}
SUBCASE("poking and peeking cart data"){
vm->vm_cartdata("dummy");
vm->vm_poke4(0x5e80, (fix32)133);
CHECK_EQ(vm->vm_peek4(0x5e80), (fix32)133);
CHECK_EQ(vm->vm_dget(32), (fix32)133);
}
SUBCASE("poking draw palette data"){
//21 : 0001 0101 (transparet: true) (color mapped 5)
vm->vm_poke(0x5f02, 21);
CHECK_EQ(memory->drawState.drawPaletteMap[2], 21);
CHECK_EQ(graphics->getDrawPalMappedColor(2), 5);
CHECK_EQ(graphics->isColorTransparent(2), true);
}
SUBCASE("poking clip data"){
//7 : 0000 0111
vm->vm_poke(0x5f21, 89);
CHECK_EQ(memory->drawState.clip_yb, 89);
}
SUBCASE("poking unknown ram at 0x5f24"){
//7 : 0000 0111
vm->vm_poke(0x5f24, 254);
CHECK_EQ(memory->drawState.unknown05f24, 254);
}
SUBCASE("poking color"){
vm->vm_poke(0x5f25, 12);
CHECK_EQ(memory->drawState.color, 12);
}
SUBCASE("poking text cursor"){
vm->vm_poke(0x5f26, 74);
CHECK_EQ(memory->drawState.text_x, 74);
}
SUBCASE("poking camera position"){
vm->vm_poke2(0x5f2a, -72);
CHECK_EQ(memory->drawState.camera_y, -72);
}
SUBCASE("poking draw mode"){
vm->vm_poke(0x5f2c, 2);
CHECK_EQ(memory->drawState.drawMode, 2);
}
SUBCASE("poking dev kit mode"){
vm->vm_poke(0x5f2d, 2);
CHECK_EQ(memory->drawState.devkitMode, 2);
}
SUBCASE("poking persist palette"){
vm->vm_poke(0x5f2e, 1);
CHECK_EQ(memory->drawState.persistPalette, 1);
}
SUBCASE("poking sound pause state"){
vm->vm_poke(0x5f2f, 4);
CHECK_EQ(memory->drawState.soundPauseState, 4);
}
SUBCASE("poking suppress pause"){
vm->vm_poke(0x5f30, 1);
CHECK_EQ(memory->drawState.suppressPause, 1);
}
SUBCASE("poking fill pattern"){
vm->vm_poke(0x5f31, 24);
CHECK_EQ(memory->drawState.fillPattern[0], 24);
}
SUBCASE("poking fill pattern transparency bit"){
vm->vm_poke(0x5f33, 1);
CHECK_EQ(memory->drawState.fillPatternTransparencyBit, 1);
}
SUBCASE("poking color setting flag"){
/*
-- bit 0x1000.0000 means the non-colour bits should be observed
-- bit 0x0100.0000 transparency bit
-- bits 0x00FF.0000 are the usual colour bits
-- bits 0x0000.FFFF are interpreted as the fill pattern
*/
vm->vm_poke(0x5f34, 36);
CHECK_EQ(memory->drawState.colorSettingFlag, 36);
}
SUBCASE("poking line invalid"){
vm->vm_poke(0x5f35, 1);
CHECK_EQ(memory->drawState.lineInvalid, 1);
}
SUBCASE("poking tline width"){
vm->vm_poke(0x5f38, 65);
CHECK_EQ(memory->drawState.tlineMapWidth, 65);
}
SUBCASE("poking tline height"){
vm->vm_poke(0x5f39, 66);
CHECK_EQ(memory->drawState.tlineMapHeight, 66);
}
SUBCASE("poking tline x"){
vm->vm_poke(0x5f3a, 67);
CHECK_EQ(memory->drawState.tlineMapXOffset, 67);
}
SUBCASE("poking tline y"){
vm->vm_poke(0x5f3b, 68);
CHECK_EQ(memory->drawState.tlineMapYOffset, 68);
}
SUBCASE("poking line x"){
vm->vm_poke2(0x5f3c, -3);
CHECK_EQ(memory->drawState.line_x, -3);
}
SUBCASE("poking line y"){
vm->vm_poke2(0x5f3e, 262);
CHECK_EQ(memory->drawState.line_y, 262);
}
SUBCASE("poking audio hardware state half rate") {
vm->vm_poke(0x5f40, 3);
CHECK_EQ(memory->hwState.half_rate, 3);
}
SUBCASE("poking audio hardware state distort") {
vm->vm_poke(0x5f42, 5);
CHECK_EQ(memory->hwState.distort, 5);
}
SUBCASE("poking rng state") {
//rng gets seeded, we need to zero it out to test this easier
memory->hwState.rngState[0] = 0;
vm->vm_poke(0x5f44, 20);
CHECK_EQ(memory->hwState.rngState[0], 20);
}
SUBCASE("poking button state doesn't affect input") {
//0000 1010
vm->vm_poke(0x5f4c, 10);
CHECK_EQ(memory->hwState.buttonStates[0], 10);
CHECK_EQ(input->btn(), 0);
}
SUBCASE("poking print attributes") {
vm->vm_poke(0x5f58, 53);
CHECK_EQ(memory->hwState.printAttributes, 53);
}
SUBCASE("poking btnp repeat delay") {
vm->vm_poke(0x5f5c, 10);
CHECK_EQ(memory->hwState.btnpRepeatDelay, 10);
}
SUBCASE("poking btnp repeat interval") {
vm->vm_poke(0x5f5d, 25);
CHECK_EQ(memory->hwState.btnpRepeatInterval, 25);
}
SUBCASE("poking color bitmask") {
vm->vm_poke(0x5f5e, 42);
CHECK_EQ(memory->hwState.colorBitmask, 42);
}
SUBCASE("poking alternate palette flag") {
vm->vm_poke(0x5f5f, 10);
CHECK_EQ(memory->hwState.alternatePaletteFlag, 10);
}
SUBCASE("poking alternate palette map") {
vm->vm_poke(0x5f60, 11);
CHECK_EQ(memory->hwState.alternatePaletteMap[0], 11);
}
SUBCASE("poking alternate palette map") {
vm->vm_poke(0x5f70, 7);
CHECK_EQ(memory->hwState.alternatePaletteScreenLineBitfield[0], 7);
}
SUBCASE("poking gpio pins") {
vm->vm_poke(0x5f80, 192);
CHECK_EQ(memory->hwState.gpioPins[0], 192);
}
SUBCASE("poking screen data (first two pixels)") {
//195: 1100 0011 (left pixel 3, right pixel 12)
vm->vm_poke(0x6000, 195);
CHECK_EQ(memory->screenBuffer[0], 195);
CHECK_EQ(graphics->pget(0, 0), 3);
CHECK_EQ(graphics->pget(1, 0), 12);
}
SUBCASE("poking screen data (last two pixels)") {
//210: 1101 0010
vm->vm_poke(0x7fff, 210);
CHECK_EQ(memory->screenBuffer[8191], 210);
CHECK_EQ(graphics->pget(126, 127), 2);
CHECK_EQ(graphics->pget(127, 127), 13);
}
SUBCASE("poking user data (first byte)") {
vm->vm_poke(0x8000, 129);
CHECK_EQ(memory->data[0x8000], 129);
CHECK_EQ(memory->userData[0], 129);
}
SUBCASE("poking user data (last byte)") {
vm->vm_poke(0xffff, 211);
CHECK_EQ(memory->data[0xffff], 211);
CHECK_EQ(memory->userData[32767], 211);
}
SUBCASE("Loading cart copies cart rom to memory") {
vm->LoadCart("cartparsetest.p8.png");
SUBCASE("Gfx data is populated") {
CHECK(memory->spriteSheetData[0] == 255);
CHECK(memory->spriteSheetData[1] == 1);
CHECK(memory->spriteSheetData[2] == 2);
CHECK(memory->spriteSheetData[3] == 3);
}
SUBCASE("Sprite flags is populated") {
CHECK(memory->spriteFlags[0] == 0);
CHECK(memory->spriteFlags[1] == 1);
}
SUBCASE("Map data is populated") {
CHECK(memory->mapData[0] == 1);
CHECK(memory->mapData[1] == 0);
}
SUBCASE("Sfx data is populated") {
CHECK(memory->sfx[0].data[0] == 13);
CHECK(memory->sfx[0].filters == 0);
CHECK(memory->sfx[0].loopRangeEnd == 0);
CHECK(memory->sfx[0].loopRangeEnd == 0);
CHECK(memory->sfx[0].speed == 29);
CHECK(memory->sfx[0].notes[0].getWaveform() == 0);
CHECK(memory->sfx[0].notes[0].getEffect() == 0);
CHECK(memory->sfx[0].notes[0].getKey() == 13);
CHECK(memory->sfx[0].notes[0].getVolume() == 3);
CHECK(memory->sfx[0].notes[0].data[0] == 13);
CHECK(memory->sfx[0].notes[0].data[1] == 6);
}
SUBCASE("Music data is populated") {
CHECK(memory->songs[0].getSfx0() == 1);
CHECK(memory->songs[0].getStart() == 0);
CHECK(memory->songs[0].getSfx1() == 66);
CHECK(memory->songs[0].getLoop() == 0);
CHECK(memory->songs[0].getSfx2() == 67);
CHECK(memory->songs[0].getStop() == 0);
CHECK(memory->songs[0].getSfx3() == 68);
CHECK(memory->songs[0].getMode() == 0);
}
}
SUBCASE("reload writes cart rom over changes") {
vm->LoadCart("cartparsetest.p8.png");
graphics->sset(0, 0, 4);
graphics->fset(0, 3);
graphics->mset(0, 0, 10);
vm->vm_reload(0, 0, 0x4300, "");
CHECK_EQ(graphics->sget(0, 0), 15);
CHECK_EQ(graphics->fget(0), 0);
CHECK_EQ(graphics->mget(0, 0), 1);
}
SUBCASE("memset sets memory") {
vm->vm_memset(0x3000, 13, 4);
CHECK_EQ(graphics->fget(0), 13);
CHECK_EQ(graphics->fget(3), 13);
}
SUBCASE("memcpy copies memory") {
vm->LoadCart("cartparsetest.p8.png");
vm->vm_memcpy(0x3000, 0, 4);
CHECK_EQ(memory->spriteSheetData[1], 1);
}
SUBCASE("dget before setting cart data is allowed for backward compat") {
vm->vm_dset(34, 1925);
CHECK_EQ(vm->vm_dget(34), (fix32)1925);
}
SUBCASE("dget after setting cart data key stores value") {
vm->vm_cartdata("uniquekey");
vm->vm_dset(34, 1923);
CHECK_EQ(vm->vm_dget(34), (fix32)1923);
}
SUBCASE("pico driller style input"){
vm->LoadCart("drillerinputtest.p8");
//this loads the cart + glue code into a coroutine, but does not execute anything yet
//should probably get renamed. Same as typing LOAD() in pico 8
vm->vm_run();
vm->Step(); //this will call the cart code, init, and update once
//30 fps game loop requires 1 step to update and update buttons, and another to draw
SUBCASE("initial state"){
bool btnbits = vm->ExecuteLua(
"function btnbitsinitialtest()\n"
" return btnbits == 0\n"
"end\n",
"btnbitsinitialtest");
CHECK(btnbits);
}
SUBCASE("no buttons gives 0"){
vm->Step();
bool btnbits = vm->ExecuteLua(
"function btnbitstest0()\n"
" return btnbits == 0\n"
"end\n",
"btnbitstest0");
CHECK(btnbits);
}
SUBCASE("left pushed returns 1"){
stubHost->stubInput(1, 1);
//requires two loops to ensure the button state is updated on 30 fps game
vm->Step();
vm->Step();
bool btnbits = vm->ExecuteLua(
"function btnbitstest1()\n"
" return btnbits == 1\n"
"end\n",
"btnbitstest1");
CHECK(btnbits);
}
SUBCASE("right pushed returns 2"){
stubHost->stubInput(2, 2);
//requires two loops to ensure the button state is updated on 30 fps game
vm->Step();
vm->Step();
bool btnbits = vm->ExecuteLua(
"function btnbitstest2()\n"
" return btnbits == 2\n"
"end\n",
"btnbitstest2");
CHECK(btnbits);
}
SUBCASE("right pushed detected by band op"){
stubHost->stubInput(2, 2);
//requires two loops to ensure the button state is updated on 30 fps game
vm->Step();
vm->Step();
bool btnbits = vm->ExecuteLua(
"function rightbandtest()\n"
" return rightband == 2\n"
"end\n",
"rightbandtest");
CHECK(btnbits);
}
vm->CloseCart();
}
SUBCASE("emoji button variables are available in sandbox"){
vm->LoadCart("emojibuttons.p8");
vm->vm_run();
vm->Step();
SUBCASE("emoji button variables resolve to correct numeric values"){
// Test that ⬅️ = 0, ➡️ = 1, ⬆️ = 2, ⬇️ = 3, 🅾️ = 4, ❎ = 5
bool left_correct = vm->ExecuteLua(
"function test_left_val()\n"
" return left_val == 0\n"
"end\n",
"test_left_val");
CHECK(left_correct);
bool right_correct = vm->ExecuteLua(
"function test_right_val()\n"
" return right_val == 1\n"
"end\n",
"test_right_val");
CHECK(right_correct);
bool up_correct = vm->ExecuteLua(
"function test_up_val()\n"
" return up_val == 2\n"
"end\n",
"test_up_val");
CHECK(up_correct);
bool down_correct = vm->ExecuteLua(
"function test_down_val()\n"
" return down_val == 3\n"
"end\n",
"test_down_val");
CHECK(down_correct);
bool o_correct = vm->ExecuteLua(
"function test_o_val()\n"
" return o_val == 4\n"
"end\n",
"test_o_val");
CHECK(o_correct);
bool x_correct = vm->ExecuteLua(
"function test_x_val()\n"
" return x_val == 5\n"
"end\n",
"test_x_val");
CHECK(x_correct);
}
SUBCASE("btn with emoji argument checks correct button"){
// Press O button (bit 4 = 16) and verify btn(🅾️) returns true but btn(❎) returns false
stubHost->stubInput(16, 16); // O button pressed
vm->Step();
vm->Step();
bool o_pressed = vm->ExecuteLua(
"function test_btn_o()\n"
" return btn_o == true\n"
"end\n",
"test_btn_o");
CHECK(o_pressed);
bool x_not_pressed = vm->ExecuteLua(
"function test_btn_x_not()\n"
" return btn_x == false\n"
"end\n",
"test_btn_x_not");
CHECK(x_not_pressed);
bool left_not_pressed = vm->ExecuteLua(
"function test_btn_left_not()\n"
" return btn_left == false\n"
"end\n",
"test_btn_left_not");
CHECK(left_not_pressed);
}
SUBCASE("left button does not trigger O or X button checks"){
// Press left button (bit 0 = 1) and verify btn(🅾️) and btn(❎) return false
stubHost->stubInput(1, 1); // Left button pressed
vm->Step();
vm->Step();
bool left_pressed = vm->ExecuteLua(
"function test_left_pressed()\n"
" return btn_left == true\n"
"end\n",
"test_left_pressed");
CHECK(left_pressed);
bool o_not_pressed = vm->ExecuteLua(
"function test_o_not_pressed()\n"
" return btn_o == false\n"
"end\n",
"test_o_not_pressed");
CHECK(o_not_pressed);
bool x_not_pressed = vm->ExecuteLua(
"function test_x_not_pressed()\n"
" return btn_x == false\n"
"end\n",
"test_x_not_pressed");
CHECK(x_not_pressed);
}
vm->CloseCart();
}
SUBCASE("togglepausemenu resets and restores draw state") {
graphics->pal(10, 12, 0);
graphics->fillp(25);
graphics->clip(11, 12, 21, 22);
graphics->camera(1, 2);
graphics->color(3);
SUBCASE("togglepausemenu resets draw state"){
vm->togglePauseMenu();
CHECK_EQ(10, graphics->getDrawPalMappedColor(10));
CHECK_EQ(0, memory->drawState.fillPattern[0]);
CHECK_EQ(0, memory->drawState.clip_xb);
CHECK_EQ(0, memory->drawState.clip_yb);
CHECK_EQ(128, memory->drawState.clip_xe);
CHECK_EQ(128, memory->drawState.clip_ye);
CHECK_EQ(0, memory->drawState.camera_x);
CHECK_EQ(0, memory->drawState.camera_y);
CHECK_EQ(6, memory->drawState.color);
}
SUBCASE("togglepausemenu second time restores draw state"){
vm->togglePauseMenu();
vm->togglePauseMenu();
CHECK_EQ(12, graphics->getDrawPalMappedColor(10));
CHECK_EQ(25, memory->drawState.fillPattern[0]);
CHECK_EQ(11, memory->drawState.clip_xb);
CHECK_EQ(12, memory->drawState.clip_yb);
CHECK_EQ(32, memory->drawState.clip_xe);
CHECK_EQ(34, memory->drawState.clip_ye);
CHECK_EQ(1, memory->drawState.camera_x);
CHECK_EQ(2, memory->drawState.camera_y);
CHECK_EQ(3, memory->drawState.color);
}
}
SUBCASE("getSerializedCartData for all zeroes"){
vm->vm_cartdata("serializeTest");
for(int i = 0; i < 256; i++) {
memory->cartData[i] = 0;
}
auto result = vm->getSerializedCartData();
std::string expected =
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n";
CHECK_EQ(expected, result);
}
SUBCASE("getSerializedCartData with 0-255"){
vm->vm_cartdata("serializeTest");
for(int i = 0; i < 256; i++) {
memory->cartData[i] = (uint8_t)i;
}
auto result = vm->getSerializedCartData();
std::string expected =
"03020100070605040b0a09080f0e0d0c13121110171615141b1a19181f1e1d1c\n"
"23222120272625242b2a29282f2e2d2c33323130373635343b3a39383f3e3d3c\n"
"43424140474645444b4a49484f4e4d4c53525150575655545b5a59585f5e5d5c\n"
"63626160676665646b6a69686f6e6d6c73727170777675747b7a79787f7e7d7c\n"
"83828180878685848b8a89888f8e8d8c93929190979695949b9a99989f9e9d9c\n"
"a3a2a1a0a7a6a5a4abaaa9a8afaeadacb3b2b1b0b7b6b5b4bbbab9b8bfbebdbc\n"
"c3c2c1c0c7c6c5c4cbcac9c8cfcecdccd3d2d1d0d7d6d5d4dbdad9d8dfdedddc\n"
"e3e2e1e0e7e6e5e4ebeae9e8efeeedecf3f2f1f0f7f6f5f4fbfaf9f8fffefdfc\n";
CHECK_EQ(expected, result);
}
SUBCASE("deserializeCartDataToMemory for all zeroes"){
vm->vm_cartdata("serializeTest");
for(int i = 0; i < 256; i++) {
memory->cartData[i] = 1;
}
std::string allZeroes =
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n";
vm->deserializeCartDataToMemory(allZeroes);
bool matches = true;
for(int i = 0; i < 256; i++) {
matches &= memory->cartData[i] == 0;
}
CHECK(matches);
}
SUBCASE("deserializeCartDataToMemory with 0-255"){
vm->vm_cartdata("serializeTest");
for(int i = 0; i < 256; i++) {
memory->cartData[i] = 1;
}
std::string values =
"03020100070605040b0a09080f0e0d0c13121110171615141b1a19181f1e1d1c\n"
"23222120272625242b2a29282f2e2d2c33323130373635343b3a39383f3e3d3c\n"
"43424140474645444b4a49484f4e4d4c53525150575655545b5a59585f5e5d5c\n"
"63626160676665646b6a69686f6e6d6c73727170777675747b7a79787f7e7d7c\n"
"83828180878685848b8a89888f8e8d8c93929190979695949b9a99989f9e9d9c\n"
"a3a2a1a0a7a6a5a4abaaa9a8afaeadacb3b2b1b0b7b6b5b4bbbab9b8bfbebdbc\n"
"c3c2c1c0c7c6c5c4cbcac9c8cfcecdccd3d2d1d0d7d6d5d4dbdad9d8dfdedddc\n"
"e3e2e1e0e7e6e5e4ebeae9e8efeeedecf3f2f1f0f7f6f5f4fbfaf9f8fffefdfc\n";
vm->deserializeCartDataToMemory(values);
bool matches = true;
for(int i = 0; i < 256; i++) {
matches &= memory->cartData[i] == i;
}
CHECK(matches);
}
SUBCASE("dset then getSerializedCartData returns correct values"){
vm->vm_cartdata("serializeTest");
vm->vm_dset(32, 128);
vm->vm_dset(0, 1);
vm->vm_dset(1, 2);
auto actual = vm->getSerializedCartData();
std::string expected =
"0001000000020000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0080000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n";
CHECK_EQ(expected, actual);
}
SUBCASE("dset then getSerializedCartData with negatives returns correct values"){
vm->vm_cartdata("serializeTest");
vm->vm_dset(0, -1);
vm->vm_dset(1, 1);
vm->vm_dset(2, 32767);
vm->vm_dset(3, -32768);
auto actual = vm->getSerializedCartData();
std::string expected =
"ffff0000000100007fff00008000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n";
CHECK_EQ(expected, actual);
}
SUBCASE("multiple cartdata keys can be used (up to 4)"){
vm->vm_cartdata("key1");
vm->vm_dset(0, 100);
vm->vm_cartdata("key2");
vm->vm_cartdata("key3");
vm->vm_cartdata("key4");
vm->vm_dset(0, 400);
CHECK_EQ(vm->vm_dget(0), (fix32)400);
vm->vm_cartdata("key2");
CHECK_EQ(vm->vm_dget(0), (fix32)0);
vm->vm_dset(0, 200);
CHECK_EQ(vm->vm_dget(0), (fix32)200);
vm->vm_cartdata("key1");
CHECK_EQ(vm->vm_dget(0), (fix32)100);
}
SUBCASE("error when trying to use more than 4 cartdata keys"){
vm->vm_cartdata("key1");
vm->vm_cartdata("key2");
vm->vm_cartdata("key3");
vm->vm_cartdata("key4");
// 5th key should fail
CHECK_FALSE(vm->vm_cartdata("key5"));
CHECK_EQ(vm->GetBiosError(), "too many cart data keys (max 4)");
}
SUBCASE("switching between different cartdata keys works correctly"){
vm->vm_cartdata("save1");
vm->vm_dset(0, 111);
vm->vm_dset(1, 222);
vm->vm_cartdata("save2");
vm->vm_dset(0, 333);
vm->vm_dset(1, 444);
vm->vm_cartdata("save1");
CHECK_EQ(vm->vm_dget(0), (fix32)111);
CHECK_EQ(vm->vm_dget(1), (fix32)222);
vm->vm_cartdata("save2");
CHECK_EQ(vm->vm_dget(0), (fix32)333);
CHECK_EQ(vm->vm_dget(1), (fix32)444);
}
SUBCASE("reusing same cartdata key multiple times works"){
// Use the same key multiple times - should work fine
vm->vm_cartdata("sharedkey");
vm->vm_dset(0, 555);
vm->vm_cartdata("sharedkey");
CHECK_EQ(vm->vm_dget(0), (fix32)555);
vm->vm_dset(1, 666);
vm->vm_cartdata("sharedkey");
CHECK_EQ(vm->vm_dget(0), (fix32)555);
CHECK_EQ(vm->vm_dget(1), (fix32)666);
}
SUBCASE("backward compatibility - single key usage still works"){
vm->vm_cartdata("singlekey");
vm->vm_dset(0, 777);
vm->vm_dset(1, 888);
CHECK_EQ(vm->vm_dget(0), (fix32)777);
CHECK_EQ(vm->vm_dget(1), (fix32)888);
vm->vm_cartdata("singlekey");
CHECK_EQ(vm->vm_dget(0), (fix32)777);
CHECK_EQ(vm->vm_dget(1), (fix32)888);
}
SUBCASE("current active key is tracked correctly for serialization"){
vm->vm_cartdata("active1");
vm->vm_dset(0, 999);
vm->vm_dset(1, 1111);
vm->vm_cartdata("active2");
vm->vm_dset(0, 2222);
vm->vm_dset(1, 3333);
vm->vm_cartdata("active1");
CHECK_EQ(vm->vm_dget(0), (fix32)999);
CHECK_EQ(vm->vm_dget(1), (fix32)1111);
vm->vm_dset(0, 4444);
vm->vm_dset(1, 5555);
auto serialized = vm->getSerializedCartData();
// 4444 = 0x115C, 5555 = 0x15B3 (in little-endian format)
std::string expected =
"115c000015b30000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n";
CHECK_EQ(expected, serialized);
}
SUBCASE("deserializeCartDataToMemory with negative ints"){
vm->vm_cartdata("serializeTest");
for(int i = 0; i < 256; i++) {
memory->cartData[i] = 0;
}
std::string values =
"ffff0000000100007fff00008000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n";
vm->deserializeCartDataToMemory(values);
CHECK_EQ(vm->vm_dget(0), (fix32)-1);
CHECK_EQ(vm->vm_dget(1), (fix32)1);
CHECK_EQ(vm->vm_dget(2), (fix32)32767);
CHECK_EQ(vm->vm_dget(3), (fix32)-32768);
}
SUBCASE("SFX Note getters match expected values"){
memory->sfx[0].notes[0].data[0] = 205;
memory->sfx[0].notes[0].data[1] = 233;
CHECK(memory->sfx[0].notes[0].getWaveform() == 7);
CHECK(memory->sfx[0].notes[0].getEffect() == 6);
CHECK(memory->sfx[0].notes[0].getKey() == 13);
CHECK(memory->sfx[0].notes[0].getVolume() == 4);
CHECK(memory->sfx[0].notes[0].getCustom() == 1);
}
SUBCASE("SFX Note settiers set correct values"){
memory->sfx[0].notes[0].setWaveform(7);
memory->sfx[0].notes[0].setEffect(6);
memory->sfx[0].notes[0].setKey(13);
memory->sfx[0].notes[0].setVolume(4);
memory->sfx[0].notes[0].setCustom(1);
CHECK(memory->sfx[0].notes[0].data[0] == 205);
CHECK(memory->sfx[0].notes[0].data[1] == 233);
}
SUBCASE("cartlist gets sorted"){
std::vector<std::string> vec = { "Qcart.p8", "Acart.p8", "Zcart.p8.png", "Jcart.p8.png" };
vm->SetCartList(vec);
auto sorted = vm->GetCartList();
CHECK_EQ("Acart.p8", sorted[0]);
CHECK_EQ("Jcart.p8.png", sorted[1]);
CHECK_EQ("Qcart.p8", sorted[2]);
CHECK_EQ("Zcart.p8.png", sorted[3]);
}
SUBCASE("reset sets hw and draw state back to defaults"){
graphics->pal(4, 5, 0);
graphics->color(14);
memory->hwState.widthOfTheMap = 17;
memory->hwState.alternatePaletteFlag = 2;
vm->vm_reset();
CHECK_EQ(128, memory->hwState.widthOfTheMap);
CHECK_EQ(6, memory->drawState.color);
CHECK_EQ(4, graphics->getDrawPalMappedColor(4));
CHECK_EQ(0, memory->hwState.alternatePaletteFlag);
}
SUBCASE("pause menu - initial state is not paused") {
CHECK(vm->IsPaused() == false);
}
SUBCASE("pause menu - togglePauseMenu toggles state") {
CHECK(vm->IsPaused() == false);
vm->togglePauseMenu();
CHECK(vm->IsPaused() == true);
vm->togglePauseMenu();
CHECK(vm->IsPaused() == false);
}
SUBCASE("picoluaapi handles negative colors correctly"){
SUBCASE("pset negative number gets set correctly"){
vm->LoadCart("cartparsetest.p8");
vm->vm_run();
vm->Step();
bool result = vm->ExecuteLua(
"function psetnegativetest()\n"
" pset(0, 0, -1)\n"
" return pget(0, 0) == 15\n"
"end\n",
"psetnegativetest");
CHECK(result);
}
SUBCASE("pal negative number gets set correctly"){
vm->LoadCart("cartpalnegativetest.p8");
vm->vm_run();
vm->Step();
bool result = vm->ExecuteLua(
"function palnegativetest()\n"
" pal(0, -1, 0)\n"
" pset(0, 0, 0)\n"
" return pget(0, 0) == 15\n"
"end\n",
"palnegativetest");
CHECK(result);
}
}
delete stubHost;
delete graphics;
delete input;
delete audio;
delete vm;
delete memory;
}