Fix DisplayGet API v2, writeMemory format, sprite address validation
- Change API_VERSION from 0x01 to 0x02 (required for DisplayGet, KeyboardFeed) - Fix writeMemory body format: add end address and bankId per VICE docs - Fix step() to use AdvanceInstructions (0x71) - no separate Step command - Add validateSpriteDataAddress() with region/severity/warning - Update readSprites to include address diagnostics in dataAddress field - Prioritize address issues over visibility issues in hints 🤖 Generated with [Claude Code](https://claude.ai/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
57c4c43b8a
commit
26b5b694c1
4 changed files with 138 additions and 15 deletions
39
src/index.ts
39
src/index.ts
|
|
@ -11,6 +11,7 @@ import {
|
||||||
getVideoAddresses,
|
getVideoAddresses,
|
||||||
getGraphicsMode,
|
getGraphicsMode,
|
||||||
isSpriteVisible,
|
isSpriteVisible,
|
||||||
|
validateSpriteDataAddress,
|
||||||
disassemble,
|
disassemble,
|
||||||
getLabelForAddress,
|
getLabelForAddress,
|
||||||
} from "./utils/index.js";
|
} from "./utils/index.js";
|
||||||
|
|
@ -1434,9 +1435,10 @@ Related tools: readVicState, readMemory (for sprite data)`,
|
||||||
|
|
||||||
const visibility = isSpriteVisible(x, y, enabled);
|
const visibility = isSpriteVisible(x, y, enabled);
|
||||||
|
|
||||||
// Sprite data address
|
// Sprite data address with region validation
|
||||||
const pointer = spritePointers[i];
|
const pointer = spritePointers[i];
|
||||||
const dataAddress = bankInfo.baseAddress + pointer * 64;
|
const dataAddress = bankInfo.baseAddress + pointer * 64;
|
||||||
|
const addressInfo = validateSpriteDataAddress(dataAddress);
|
||||||
|
|
||||||
sprites.push({
|
sprites.push({
|
||||||
index: i,
|
index: i,
|
||||||
|
|
@ -1459,16 +1461,40 @@ Related tools: readVicState, readMemory (for sprite data)`,
|
||||||
dataAddress: {
|
dataAddress: {
|
||||||
value: dataAddress,
|
value: dataAddress,
|
||||||
hex: `$${dataAddress.toString(16).padStart(4, "0")}`,
|
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 enabledCount = sprites.filter((s) => s.enabled).length;
|
||||||
const visibleCount = sprites.filter((s) => s.position.visible).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)
|
.filter((s) => s.enabled && !s.position.visible)
|
||||||
.map((s) => `Sprite ${s.index}: ${s.position.visibilityReason}`);
|
.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({
|
return formatResponse({
|
||||||
count: sprites.length,
|
count: sprites.length,
|
||||||
enabledCount,
|
enabledCount,
|
||||||
|
|
@ -1476,13 +1502,8 @@ Related tools: readVicState, readMemory (for sprite data)`,
|
||||||
sprites,
|
sprites,
|
||||||
spriteMulticolor0: getColorInfo(vicData[0x25]),
|
spriteMulticolor0: getColorInfo(vicData[0x25]),
|
||||||
spriteMulticolor1: getColorInfo(vicData[0x26]),
|
spriteMulticolor1: getColorInfo(vicData[0x26]),
|
||||||
issues: issues.length > 0 ? issues : undefined,
|
issues: allIssues.length > 0 ? allIssues : undefined,
|
||||||
hint:
|
hint,
|
||||||
issues.length > 0
|
|
||||||
? `${issues.length} enabled sprite(s) not visible: ${issues[0]}`
|
|
||||||
: enabledCount === 0
|
|
||||||
? "No sprites enabled"
|
|
||||||
: `${enabledCount} sprite(s) enabled, ${visibleCount} visible`,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return formatError(error as ViceError);
|
return formatError(error as ViceError);
|
||||||
|
|
|
||||||
|
|
@ -455,13 +455,16 @@ export class ViceClient {
|
||||||
// Ensure VICE is stopped before memory write
|
// Ensure VICE is stopped before memory write
|
||||||
await this.ensureStopped();
|
await this.ensureStopped();
|
||||||
|
|
||||||
// Build request: side_effects(1) + start(2) + memspace(1) + length-1(1) + data(N)
|
// Build request per official VICE docs:
|
||||||
const body = Buffer.alloc(5 + dataBuffer.length);
|
// 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[0] = 0; // No side effects
|
||||||
body.writeUInt16LE(address, 1);
|
body.writeUInt16LE(address, 1);
|
||||||
body[3] = memspace;
|
body.writeUInt16LE(endAddress, 3);
|
||||||
body[4] = dataBuffer.length - 1;
|
body[5] = memspace;
|
||||||
dataBuffer.copy(body, 5);
|
body.writeUInt16LE(0, 6); // bankId = 0 (default bank)
|
||||||
|
dataBuffer.copy(body, 8);
|
||||||
|
|
||||||
await this.sendCommand(Command.MemorySet, body);
|
await this.sendCommand(Command.MemorySet, body);
|
||||||
}
|
}
|
||||||
|
|
@ -509,6 +512,7 @@ export class ViceClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async step(count = 1, stepOver = false): Promise<ViceResponse> {
|
async step(count = 1, stepOver = false): Promise<ViceResponse> {
|
||||||
|
// Uses AdvanceInstructions (0x71) - there is no separate Step command in VICE
|
||||||
const body = Buffer.alloc(3);
|
const body = Buffer.alloc(3);
|
||||||
body[0] = stepOver ? 1 : 0;
|
body[0] = stepOver ? 1 : 0;
|
||||||
body.writeUInt16LE(count, 1);
|
body.writeUInt16LE(count, 1);
|
||||||
|
|
@ -518,6 +522,7 @@ export class ViceClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async advanceInstructions(count: number, stepOver = false): Promise<ViceResponse> {
|
async advanceInstructions(count: number, stepOver = false): Promise<ViceResponse> {
|
||||||
|
// Alias for step() - kept for API compatibility
|
||||||
const body = Buffer.alloc(3);
|
const body = Buffer.alloc(3);
|
||||||
body[0] = stepOver ? 1 : 0;
|
body[0] = stepOver ? 1 : 0;
|
||||||
body.writeUInt16LE(count, 1);
|
body.writeUInt16LE(count, 1);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
// API Constants
|
// API Constants
|
||||||
export const STX = 0x02;
|
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)
|
// Command codes (per official VICE manual: https://vice-emu.sourceforge.io/vice_13.html)
|
||||||
export enum Command {
|
export enum Command {
|
||||||
|
|
|
||||||
|
|
@ -178,3 +178,100 @@ export function describeAddress(address: number): string {
|
||||||
if (address >= 0xe000) return "KERNAL ROM / RAM";
|
if (address >= 0xe000) return "KERNAL ROM / RAM";
|
||||||
return "";
|
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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue