From 07fec754eb5c0c68b50ee36ded86f57c9a590226 Mon Sep 17 00:00:00 2001 From: John Hardy Date: Tue, 10 Feb 2026 00:50:02 +1100 Subject: [PATCH] Persist SD writes and extend SD SPI tests --- src/platforms/tec1g/runtime.ts | 16 ++++++++++- src/platforms/tec1g/sd-spi.ts | 8 ++++++ tests/platforms/tec1g/sd-spi-runtime.test.ts | 3 ++ tests/platforms/tec1g/sd-spi.test.ts | 30 +++++++++++++++++++- 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/platforms/tec1g/runtime.ts b/src/platforms/tec1g/runtime.ts index adeda9a..6f6c30f 100644 --- a/src/platforms/tec1g/runtime.ts +++ b/src/platforms/tec1g/runtime.ts @@ -365,7 +365,21 @@ export function createTec1gRuntime( } } const sdSpi = sdEnabled - ? new SdSpi({ highCapacity: sdHighCapacity, ...(sdImage ? { image: sdImage } : {}) }) + ? new SdSpi({ + highCapacity: sdHighCapacity, + ...(sdImage ? { image: sdImage } : {}), + ...(sdImagePath && sdImage + ? { + onWrite: (image) => { + try { + fs.writeFileSync(sdImagePath, image); + } catch { + // Ignore persistence failures; runtime continues with in-memory image. + } + }, + } + : {}), + }) : null; let cartridgePresentDefault = config.cartridgeHex !== undefined; const state: Tec1gState = { diff --git a/src/platforms/tec1g/sd-spi.ts b/src/platforms/tec1g/sd-spi.ts index 9196851..49a9fac 100644 --- a/src/platforms/tec1g/sd-spi.ts +++ b/src/platforms/tec1g/sd-spi.ts @@ -13,6 +13,7 @@ export type SdSpiOptions = { csActiveLow?: boolean; highCapacity?: boolean; image?: Uint8Array; + onWrite?: (image: Uint8Array) => void; }; const MOSI_BIT = 0x01; @@ -37,6 +38,7 @@ export class SdSpi { private lastCommand: SdCommand | undefined; private outputQueue: number[] = []; private image: Uint8Array | null = null; + private onWrite?: (image: Uint8Array) => void; private pendingResponse: number[] | null = null; private delayBytes = 0; private appCommand = false; @@ -60,6 +62,9 @@ export class SdSpi { this.csActiveLow = options.csActiveLow !== false; this.highCapacity = options.highCapacity !== false; this.image = options.image ?? null; + if (options.onWrite !== undefined) { + this.onWrite = options.onWrite; + } } /** @@ -355,6 +360,9 @@ export class SdSpi { this.image[idx] = this.writeState.buffer[i] ?? 0x00; } } + if (this.onWrite) { + this.onWrite(this.image); + } } // Data response token: 0bxxx00101 = 0x05 (accepted). this.pendingResponse = [0x05, 0xff]; diff --git a/tests/platforms/tec1g/sd-spi-runtime.test.ts b/tests/platforms/tec1g/sd-spi-runtime.test.ts index 688640f..1888281 100644 --- a/tests/platforms/tec1g/sd-spi-runtime.test.ts +++ b/tests/platforms/tec1g/sd-spi-runtime.test.ts @@ -155,5 +155,8 @@ describe('TEC-1G SD SPI runtime', () => { expect(readByte(rt)).toBe(0xde); expect(readByte(rt)).toBe(0xad); expect(readByte(rt)).toBe(0xbe); + sendCommand(rt, [0x4d, 0x00, 0x00, 0x00, 0x00, 0xff]); + expect(readResponse(rt)).toBe(0x00); + expect(readByte(rt)).toBe(0x00); }); }); diff --git a/tests/platforms/tec1g/sd-spi.test.ts b/tests/platforms/tec1g/sd-spi.test.ts index babbc4b..4e50ac9 100644 --- a/tests/platforms/tec1g/sd-spi.test.ts +++ b/tests/platforms/tec1g/sd-spi.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { SdSpi } from '../../../src/platforms/tec1g/sd-spi'; const MOSI_BIT = 0x01; @@ -179,6 +179,34 @@ describe('SdSpi', () => { expect(image[2]).toBe(0x56); }); + it('invokes onWrite callback after CMD24 completes', () => { + const image = new Uint8Array(1024); + const payload = new Uint8Array(512); + payload[0] = 0xde; + payload[1] = 0xad; + payload[2] = 0xbe; + const onWrite = vi.fn(); + const spi = new SdSpi({ csMask: CS_BIT, image, onWrite }); + writeSpi(spi, 0x00); + sendCommand(spi, [0x77, 0x00, 0x00, 0x00, 0x00, 0x65]); + readResponseByte(spi); + sendCommand(spi, [0x69, 0x40, 0x00, 0x00, 0x00, 0x77]); + readResponseByte(spi); + sendCommand(spi, [0x77, 0x00, 0x00, 0x00, 0x00, 0x65]); + readResponseByte(spi); + sendCommand(spi, [0x69, 0x40, 0x00, 0x00, 0x00, 0x77]); + readResponseByte(spi); + sendCommand(spi, [0x58, 0x00, 0x00, 0x00, 0x00, 0xff]); + expect(readResponseByte(spi)).toBe(0x00); + writeDataBlock(spi, payload); + expect(readResponseByte(spi)).toBe(0x05); + expect(image[0]).toBe(0xde); + expect(image[1]).toBe(0xad); + expect(image[2]).toBe(0xbe); + expect(onWrite).toHaveBeenCalledTimes(1); + expect(onWrite).toHaveBeenCalledWith(image); + }); + it('responds to CMD13 with status', () => { const spi = new SdSpi({ csMask: CS_BIT }); writeSpi(spi, 0x00);