diff --git a/src/index.ts b/src/index.ts index 51a4c15..b28f26b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import { getVideoAddresses, getGraphicsMode, isSpriteVisible, + validateSpriteDataAddress, disassemble, getLabelForAddress, } from "./utils/index.js"; @@ -1434,9 +1435,10 @@ Related tools: readVicState, readMemory (for sprite data)`, const visibility = isSpriteVisible(x, y, enabled); - // Sprite data address + // Sprite data address with region validation const pointer = spritePointers[i]; const dataAddress = bankInfo.baseAddress + pointer * 64; + const addressInfo = validateSpriteDataAddress(dataAddress); sprites.push({ index: i, @@ -1459,16 +1461,40 @@ Related tools: readVicState, readMemory (for sprite data)`, dataAddress: { value: dataAddress, hex: `$${dataAddress.toString(16).padStart(4, "0")}`, + region: addressInfo.region, + severity: addressInfo.severity, + warning: addressInfo.warning, }, }); } const enabledCount = sprites.filter((s) => s.enabled).length; const visibleCount = sprites.filter((s) => s.position.visible).length; - const issues = sprites + + // Collect visibility issues + const visibilityIssues = sprites .filter((s) => s.enabled && !s.position.visible) .map((s) => `Sprite ${s.index}: ${s.position.visibilityReason}`); + // Collect data address issues (warnings and errors) + const addressIssues = sprites + .filter((s) => s.enabled && s.dataAddress.warning) + .map((s) => `Sprite ${s.index}: ${s.dataAddress.warning} (${s.dataAddress.hex})`); + + const allIssues = [...visibilityIssues, ...addressIssues]; + + // Build hint based on most critical issue + let hint: string; + if (addressIssues.length > 0) { + hint = `⚠️ ${addressIssues.length} sprite(s) with suspicious data address: ${addressIssues[0]}`; + } else if (visibilityIssues.length > 0) { + hint = `${visibilityIssues.length} enabled sprite(s) not visible: ${visibilityIssues[0]}`; + } else if (enabledCount === 0) { + hint = "No sprites enabled"; + } else { + hint = `${enabledCount} sprite(s) enabled, ${visibleCount} visible`; + } + return formatResponse({ count: sprites.length, enabledCount, @@ -1476,13 +1502,8 @@ Related tools: readVicState, readMemory (for sprite data)`, sprites, spriteMulticolor0: getColorInfo(vicData[0x25]), spriteMulticolor1: getColorInfo(vicData[0x26]), - issues: issues.length > 0 ? issues : undefined, - hint: - issues.length > 0 - ? `${issues.length} enabled sprite(s) not visible: ${issues[0]}` - : enabledCount === 0 - ? "No sprites enabled" - : `${enabledCount} sprite(s) enabled, ${visibleCount} visible`, + issues: allIssues.length > 0 ? allIssues : undefined, + hint, }); } catch (error) { return formatError(error as ViceError); diff --git a/src/protocol/client.ts b/src/protocol/client.ts index 5ecf276..042ed49 100644 --- a/src/protocol/client.ts +++ b/src/protocol/client.ts @@ -455,13 +455,16 @@ export class ViceClient { // Ensure VICE is stopped before memory write await this.ensureStopped(); - // Build request: side_effects(1) + start(2) + memspace(1) + length-1(1) + data(N) - const body = Buffer.alloc(5 + dataBuffer.length); + // Build request per official VICE docs: + // side_effects(1) + start(2) + end(2) + memspace(1) + bankId(2) + data(N) = 8 byte header + data + const endAddress = address + dataBuffer.length - 1; + const body = Buffer.alloc(8 + dataBuffer.length); body[0] = 0; // No side effects body.writeUInt16LE(address, 1); - body[3] = memspace; - body[4] = dataBuffer.length - 1; - dataBuffer.copy(body, 5); + body.writeUInt16LE(endAddress, 3); + body[5] = memspace; + body.writeUInt16LE(0, 6); // bankId = 0 (default bank) + dataBuffer.copy(body, 8); await this.sendCommand(Command.MemorySet, body); } @@ -509,6 +512,7 @@ export class ViceClient { } async step(count = 1, stepOver = false): Promise { + // Uses AdvanceInstructions (0x71) - there is no separate Step command in VICE const body = Buffer.alloc(3); body[0] = stepOver ? 1 : 0; body.writeUInt16LE(count, 1); @@ -518,6 +522,7 @@ export class ViceClient { } async advanceInstructions(count: number, stepOver = false): Promise { + // Alias for step() - kept for API compatibility const body = Buffer.alloc(3); body[0] = stepOver ? 1 : 0; body.writeUInt16LE(count, 1); diff --git a/src/protocol/types.ts b/src/protocol/types.ts index b90d192..ed12a4c 100644 --- a/src/protocol/types.ts +++ b/src/protocol/types.ts @@ -2,7 +2,7 @@ // API Constants export const STX = 0x02; -export const API_VERSION = 0x01; // VICE 3.x uses API v1 +export const API_VERSION = 0x02; // VICE 3.5+ uses API v2 (required for DisplayGet, KeyboardFeed) // Command codes (per official VICE manual: https://vice-emu.sourceforge.io/vice_13.html) export enum Command { diff --git a/src/utils/c64.ts b/src/utils/c64.ts index 4824a45..3e508e6 100644 --- a/src/utils/c64.ts +++ b/src/utils/c64.ts @@ -178,3 +178,100 @@ export function describeAddress(address: number): string { if (address >= 0xe000) return "KERNAL ROM / RAM"; return ""; } + +// Severity levels for address validation +export type AddressSeverity = "ok" | "warning" | "error"; + +// Sprite data address validation +// Returns region info and severity for sprite data addresses +export interface SpriteDataAddressInfo { + region: string; + severity: AddressSeverity; + warning?: string; +} + +export function validateSpriteDataAddress(address: number): SpriteDataAddressInfo { + // Zero page - very wrong for sprite data + if (address < 0x0100) { + return { + region: "Zero page", + severity: "error", + warning: "Sprite data in zero page - likely wrong pointer", + }; + } + + // Stack - definitely wrong + if (address < 0x0200) { + return { + region: "Stack", + severity: "error", + warning: "Sprite data in stack area - definitely wrong", + }; + } + + // BASIC program area - might conflict with code + if (address >= 0x0801 && address < 0x2000) { + return { + region: "BASIC program area", + severity: "warning", + warning: "Sprite data in BASIC area - may conflict with program", + }; + } + + // Default screen RAM - usually wrong + if (address >= 0x0400 && address < 0x0800) { + return { + region: "Default screen RAM", + severity: "warning", + warning: "Sprite data in screen RAM - probably wrong", + }; + } + + // Common sprite data areas - OK + if (address >= 0x2000 && address < 0x4000) { + return { + region: "Common sprite area", + severity: "ok", + }; + } + + // VIC bank 1 sprite area - OK + if (address >= 0x4000 && address < 0x8000) { + return { + region: "VIC bank 1 sprite area", + severity: "ok", + }; + } + + // VIC bank 2 - has ROM shadow issues + if (address >= 0x8000 && address < 0xc000) { + return { + region: "VIC bank 2", + severity: "ok", + }; + } + + // I/O area - cannot store sprite data here + if (address >= 0xd000 && address < 0xe000) { + return { + region: "I/O space", + severity: "error", + warning: "Cannot store sprite data in I/O space", + }; + } + + // High memory - ROM shadow area + if (address >= 0xe000) { + return { + region: "KERNAL ROM area", + severity: "warning", + warning: "Sprite data may conflict with KERNAL ROM shadow", + }; + } + + // Other areas + return { + region: describeAddress(address) || "RAM", + severity: "ok", + }; +}