@henols/vice-mcp 0.2.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +6 -4
- package/anno-cli.ts +1 -1
- package/anno-store.ts +1 -1
- package/backend-detect.mts +143 -19
- package/build.ts +42 -1
- package/evid-ingest.ts +7 -3
- package/host-tool-client.ts +7 -3
- package/package.json +3 -1
- package/prerequisites.json +416 -0
- package/prg-image.ts +1 -1
- package/repo-root.ts +19 -8
- package/resources/backend-detect.mjs +90 -18
- package/resources/broker-launch.mjs +11 -2
- package/resources/ghidra-project.mjs +2 -2
- package/resources/host-tool.mjs +306 -82
- package/resources/prerequisites.json +416 -0
- package/resources/tool-location.mjs +750 -0
- package/resources/vice-broker.mjs +85 -12
- package/stock-checkpoints.ts +2 -2
- package/stock-connect.ts +12 -10
- package/stock-execution.ts +4 -2
- package/stock-memory-search.ts +3 -2
- package/stock-protocol.ts +60 -28
- package/stock-registers.ts +3 -1
- package/text-protocol.ts +6 -3
- package/textmon-profile.ts +8 -3
- package/version.ts +34 -246
- package/vice-proxy.ts +23 -23
|
@@ -139,9 +139,6 @@ function resolveCeilingForRecord() {
|
|
|
139
139
|
const n = Number(raw);
|
|
140
140
|
return Number.isFinite(n) ? n : 16;
|
|
141
141
|
}
|
|
142
|
-
function resolveViceBinForHostState() {
|
|
143
|
-
return process.env.VICE_BIN ?? "x64sc";
|
|
144
|
-
}
|
|
145
142
|
/** Duplicates vice-broker-client.ts's readBrokerLiveness() classification
|
|
146
143
|
* logic (never_started / stale / alive against BROKER_STALE_MS) rather than
|
|
147
144
|
* importing it -- confirmed empirically (plan 02's own SUMMARY) that
|
|
@@ -218,10 +215,17 @@ function writeBrokerRecordFile(stateDir, record) {
|
|
|
218
215
|
* caller-supplied `stdio`. Merging in the other order would silently
|
|
219
216
|
* redirect a launch's output away from the per-instance log file the
|
|
220
217
|
* epoch record names, breaking the per-instance forensic logs while
|
|
221
|
-
* appearing to work.
|
|
222
|
-
|
|
218
|
+
* appearing to work.
|
|
219
|
+
*
|
|
220
|
+
* `viceBin` (Phase 60, LOC-02) names the ALREADY-resolved binary this launch
|
|
221
|
+
* is about to spawn -- the log filename's own stem, via `basename()`, rather
|
|
222
|
+
* than a fresh read of the emulator environment variable on its own.
|
|
223
|
+
* Optional, defaulting to the literal "x64sc" for a caller (a test) that
|
|
224
|
+
* supplies neither this nor a real resolution up its own call chain, so the
|
|
225
|
+
* filename shape is unchanged for it. */
|
|
226
|
+
function makeLoggingSpawn(logDir, viceBin) {
|
|
223
227
|
mkdirSync(logDir, { recursive: true });
|
|
224
|
-
const viceBinForLog = basename(
|
|
228
|
+
const viceBinForLog = basename(viceBin ?? "x64sc");
|
|
225
229
|
const logName = `${viceBinForLog}-${Date.now()}.log`;
|
|
226
230
|
const logFd = openSync(join(logDir, logName), "a");
|
|
227
231
|
return {
|
|
@@ -291,13 +295,25 @@ function writeEpochForLaunch(record, logRelPath) {
|
|
|
291
295
|
* always `undefined` in production right now; the parameter exists so that
|
|
292
296
|
* adding one later cannot reintroduce exactly this divergence between a
|
|
293
297
|
* launch's argv and its respawn's argv. */
|
|
294
|
-
|
|
298
|
+
/** Phase 60 (LOC-01/LOC-02): `viceBin` is now a REQUIRED-in-spirit fourth
|
|
299
|
+
* argument (kept optional only for source compatibility with a caller that
|
|
300
|
+
* has none to give) -- before this plan, a crash respawn's own
|
|
301
|
+
* SuperviseChildDeps.viceBin was silently left unset here, and
|
|
302
|
+
* broker-launch.mts's own launchSupervised() covered the gap by falling
|
|
303
|
+
* through to a fresh read of the emulator environment variable. That fallback is gone (see
|
|
304
|
+
* broker-launch.mts's own header for why); leaving this builder unchanged
|
|
305
|
+
* would have made a crash-respawned instance silently spawn the bare
|
|
306
|
+
* "x64sc" literal instead of the SAME binary a tools.json entry or
|
|
307
|
+
* VICE_BIN resolved for the launch it replaces -- exactly the
|
|
308
|
+
* disagreement LOC-02 exists to remove. */
|
|
309
|
+
function superviseDepsFor(stateDir, state, backend, viceBin, binmonHost) {
|
|
295
310
|
return {
|
|
296
311
|
state,
|
|
297
312
|
stateDir,
|
|
298
313
|
epoch: { epochPathFor, instanceLogDirFor, nextEpochFor, writeEpochRecord },
|
|
299
314
|
log: (line) => process.stderr.write(`${line}\n`),
|
|
300
315
|
backend,
|
|
316
|
+
viceBin,
|
|
301
317
|
binmonHost,
|
|
302
318
|
};
|
|
303
319
|
}
|
|
@@ -526,6 +542,10 @@ export async function handleAcquire(requestId, stateDir, state, deps = {}) {
|
|
|
526
542
|
// verdict handleAcquire already uses for buildViceArgs() -- on stock the port
|
|
527
543
|
// speaks the binary monitor, so an HTTP POST there can never succeed.
|
|
528
544
|
const backend = deps.backend ?? "stock";
|
|
545
|
+
// Same threaded-down-once discipline as `backend` immediately above --
|
|
546
|
+
// this function never resolves it itself. See HandleAcquireDeps.viceBin's
|
|
547
|
+
// own doc comment.
|
|
548
|
+
const viceBin = deps.viceBin;
|
|
529
549
|
const probe = deps.probe ?? ((port) => probeReady(port, { backend }));
|
|
530
550
|
// Textually a verifiedKill( call site, not merely a reference -- reused
|
|
531
551
|
// UNCHANGED from broker-kill.mts, never re-derived, and never replaced by
|
|
@@ -560,6 +580,11 @@ export async function handleAcquire(requestId, stateDir, state, deps = {}) {
|
|
|
560
580
|
// supervision deps below, so a crash-respawn of this instance can never
|
|
561
581
|
// build a different backend's argv than the launch it replaces.
|
|
562
582
|
backend,
|
|
583
|
+
// The SAME local `viceBin` const, threaded down unchanged -- see this
|
|
584
|
+
// function's own binding above and HandleAcquireDeps.viceBin's doc
|
|
585
|
+
// comment. `undefined` here is exactly what broker-launch.mts's own
|
|
586
|
+
// "x64sc"-literal default is for.
|
|
587
|
+
viceBin,
|
|
563
588
|
allocateRemoteMonitorPort: deps.allocateRemoteMonitorPort,
|
|
564
589
|
// The profile the warm arm just
|
|
565
590
|
// refused to compromise on reaches buildViceArgs() here, and is
|
|
@@ -572,9 +597,9 @@ export async function handleAcquire(requestId, stateDir, state, deps = {}) {
|
|
|
572
597
|
spawnFactory: deps.buildColdSpawnFactory ??
|
|
573
598
|
((port) => {
|
|
574
599
|
const supervisorDir = join(stateDir, String(port));
|
|
575
|
-
const { spawn, logRelPath } = makeLoggingSpawn(join(supervisorDir, "logs"));
|
|
600
|
+
const { spawn, logRelPath } = makeLoggingSpawn(join(supervisorDir, "logs"), viceBin);
|
|
576
601
|
lastLogRelPath = logRelPath;
|
|
577
|
-
return withCrashSupervision("acquire", port, spawn, superviseDepsFor(stateDir, state, backend));
|
|
602
|
+
return withCrashSupervision("acquire", port, spawn, superviseDepsFor(stateDir, state, backend, viceBin));
|
|
578
603
|
}),
|
|
579
604
|
});
|
|
580
605
|
if (!result.ok) {
|
|
@@ -947,10 +972,38 @@ async function run(args) {
|
|
|
947
972
|
// There is nothing left to detect -- the resolved
|
|
948
973
|
// `backend` is always `"stock"`; what this call still does is resolve the
|
|
949
974
|
// binary's own identity for the log line below and initialise the
|
|
950
|
-
// capability cache backend-detect.mts's own record depends on.
|
|
951
|
-
|
|
975
|
+
// capability cache backend-detect.mts's own record depends on. Phase 60
|
|
976
|
+
// (LOC-01/LOC-02): `toolsDir`/`projectRoot` are passed explicitly rather
|
|
977
|
+
// than left to backend-detect.mts's own supervisorDir-derived fallback
|
|
978
|
+
// (PD-03) -- this IS the one real call site with a genuine repoRoot to
|
|
979
|
+
// hand it, so there is no reason to make it guess. This is the ONE call
|
|
980
|
+
// in the whole real broker that ever reaches the tool-location seam; the
|
|
981
|
+
// resolved value below is threaded down through every real launch call
|
|
982
|
+
// site from here, never re-resolved per acquire.
|
|
983
|
+
const backendResult = resolvedBackend({
|
|
984
|
+
supervisorDir: args.stateDir,
|
|
985
|
+
toolsDir: join(args.repoRoot, ".c64-re-tools"),
|
|
986
|
+
projectRoot: args.repoRoot,
|
|
987
|
+
});
|
|
952
988
|
const backend = backendResult.backend;
|
|
989
|
+
// The ONE value this process ever spawns as the emulator binary --
|
|
990
|
+
// resolved once, here, and threaded down through HandleAcquireDeps.viceBin
|
|
991
|
+
// (below) and onHostState's own `viceBin` field (task 2). Never re-read
|
|
992
|
+
// from resolvedBackend() a second time and never re-derived locally.
|
|
993
|
+
const resolvedViceBin = backendResult.binPath;
|
|
953
994
|
process.stderr.write(`vice-broker: backend "${backend}" (binary: ${backendResult.binPath})\n`);
|
|
995
|
+
// Phase 60 gap closure (LOC-03, PD-13/T-60-15): written only when the
|
|
996
|
+
// tool-location seam refused a declared environment-variable override --
|
|
997
|
+
// `backendResult.locationRefusal` is `null` in every other case, including
|
|
998
|
+
// the PD-02 injected-override branch, which never reaches the seam at
|
|
999
|
+
// all. This is the ONE readable record a host operator has of "which
|
|
1000
|
+
// binary did this broker refuse to substitute, and why" -- no throw here:
|
|
1001
|
+
// this process's own uncaught-exception handlers kill the whole pool, and
|
|
1002
|
+
// only one of this broker's callers (the emulator spawn itself) needed
|
|
1003
|
+
// this value to be correct.
|
|
1004
|
+
if (backendResult.locationRefusal !== null) {
|
|
1005
|
+
process.stderr.write(`vice-broker: ${backendResult.locationRefusal}\n`);
|
|
1006
|
+
}
|
|
954
1007
|
// THE BROKER mints/verifies the
|
|
955
1008
|
// Ghidra runs-root handle here -- after the unconditional startup reap
|
|
956
1009
|
// above, and BEFORE the control listener below accepts a single
|
|
@@ -989,6 +1042,20 @@ async function run(args) {
|
|
|
989
1042
|
token,
|
|
990
1043
|
onAcquire: (requestId, profile) => handleAcquire(requestId, args.stateDir, state, {
|
|
991
1044
|
backend,
|
|
1045
|
+
// The ONCE-resolved `resolvedViceBin` local from this function's
|
|
1046
|
+
// own top (LOC-01/LOC-02) -- found missing here by Plan 60-05's
|
|
1047
|
+
// own required full-suite baseline diff (broker-e2e.test.ts's
|
|
1048
|
+
// "wired disconnect-while-queued" case): this real onAcquire
|
|
1049
|
+
// wiring is the ONE production call site that turns a tools.json
|
|
1050
|
+
// or VICE_BIN resolution into what the broker actually spawns,
|
|
1051
|
+
// and it was never supplying `viceBin` at all -- every unit test
|
|
1052
|
+
// calling handleAcquire() directly injects `viceBin` itself, so
|
|
1053
|
+
// this gap was invisible until an end-to-end, real-process test
|
|
1054
|
+
// exercised the genuine `run()` wiring. Without this, every real
|
|
1055
|
+
// acquire silently fell through to broker-launch.mts's own
|
|
1056
|
+
// "x64sc"-literal last-resort default, regardless of what
|
|
1057
|
+
// tools.json or VICE_BIN named.
|
|
1058
|
+
viceBin: resolvedViceBin,
|
|
992
1059
|
// Threaded down to
|
|
993
1060
|
// acquirePortAndLaunch()'s own gate (backend === "stock"); this
|
|
994
1061
|
// callback does not re-read any environment variable itself.
|
|
@@ -1026,7 +1093,13 @@ async function run(args) {
|
|
|
1026
1093
|
pid: process.pid,
|
|
1027
1094
|
startedAt,
|
|
1028
1095
|
nodeVersion: process.version,
|
|
1029
|
-
|
|
1096
|
+
// The SAME value THIS process just spawned (this function's own
|
|
1097
|
+
// `resolvedViceBin` closure binding, above) -- the deleted
|
|
1098
|
+
// per-host-state helper this file used to call here re-read the
|
|
1099
|
+
// emulator environment variable fresh on every call, which could
|
|
1100
|
+
// report a DIFFERENT binary than the one this broker actually
|
|
1101
|
+
// launched -- exactly the disagreement LOC-02 exists to remove.
|
|
1102
|
+
viceBin: resolvedViceBin,
|
|
1030
1103
|
maxInstances: resolveCeilingForRecord(),
|
|
1031
1104
|
basePort: resolveBasePort(),
|
|
1032
1105
|
// The verdict THIS process resolved once, at
|
package/stock-checkpoints.ts
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
// and a re-set must be refused rather than silently doubling up. Separately,
|
|
15
15
|
// a `stop:false` checkpoint emits a CHECKPOINT_INFO frame per hit
|
|
16
16
|
// SYNCHRONOUSLY, from inside the emulator's CPU loop, over the blocking
|
|
17
|
-
// monitor socket
|
|
18
|
-
// calls mon_breakpoint_event() before checking cp->stop
|
|
17
|
+
// monitor socket -- confirmed directly from VICE's own source: mon_breakpoint.c:557-562
|
|
18
|
+
// calls mon_breakpoint_event() before checking cp->stop -- so on a hot address
|
|
19
19
|
// this can stall the emulator thread and deadlock this client. Both guards
|
|
20
20
|
// are correctness-as-safety issues, not polish, so they belong in the same
|
|
21
21
|
// module as the tools that create the hazard.
|
package/stock-connect.ts
CHANGED
|
@@ -67,8 +67,9 @@ export interface StockConnectBrokerControl {
|
|
|
67
67
|
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
69
|
// Capabilities -- BACK-04's version-gated answer set. Today this is just
|
|
70
|
-
// CPUHISTORY_GET's three-way outcome
|
|
71
|
-
//
|
|
70
|
+
// CPUHISTORY_GET's three-way outcome, confirmed by probing both a stock
|
|
71
|
+
// 3.9 build and the fork's 3.10-vintage build over the wire (D-10): 0x00 OK
|
|
72
|
+
// on the 3.10 fork, 0x83 INVALID_TYPE (opcode absent) on
|
|
72
73
|
// stock 3.9, 0x8f CMD_FAILURE (compiled without support) as the distinct
|
|
73
74
|
// third case. A later plan adding a second version-gated opcode extends this
|
|
74
75
|
// type, not a parallel mechanism.
|
|
@@ -327,12 +328,12 @@ async function safeDisconnect(client: ViceMonitorClient): Promise<void> {
|
|
|
327
328
|
|
|
328
329
|
/**
|
|
329
330
|
* CR-02 (code review 2026-08-13). THE load-bearing counterpart to every
|
|
330
|
-
* command this handshake sends.
|
|
331
|
-
*
|
|
332
|
-
* `monitor_startup_trap()` on ANY INBOUND BYTE (monitor_binary.c:281),
|
|
333
|
-
* that check runs every vsync -- so the bare `PING` (0x81) below halts
|
|
334
|
-
* emulated C64 within roughly one frame and emits `STOPPED` (0x62).
|
|
335
|
-
* (0xaa) is the ONLY thing that resumes it.
|
|
331
|
+
* command this handshake sends. Confirmed by reading directly from VICE's
|
|
332
|
+
* own source rather than any external summary: `monitor_check_binary()`
|
|
333
|
+
* calls `monitor_startup_trap()` on ANY INBOUND BYTE (monitor_binary.c:281),
|
|
334
|
+
* and that check runs every vsync -- so the bare `PING` (0x81) below halts
|
|
335
|
+
* the emulated C64 within roughly one frame and emits `STOPPED` (0x62).
|
|
336
|
+
* `EXIT` (0xaa) is the ONLY thing that resumes it.
|
|
336
337
|
*
|
|
337
338
|
* Before this function existed, nothing in this tree ever sent 0xaa: the
|
|
338
339
|
* first `vice_ping` on the stock backend froze the machine and left it frozen
|
|
@@ -431,8 +432,9 @@ export async function stockConnect({ host, port, targetId, brokerControl, deps =
|
|
|
431
432
|
// Step 3: api_version assertion. A non-0x02 api_version rejects this
|
|
432
433
|
// send() call directly with a StockFramingError naming the observed
|
|
433
434
|
// value -- see this function's own header comment. NOTE (CR-02): this
|
|
434
|
-
// single byte HALTS the emulated machine (any inbound byte does
|
|
435
|
-
//
|
|
435
|
+
// single byte HALTS the emulated machine (any inbound byte does, per
|
|
436
|
+
// monitor_check_binary()'s own vsync-driven trap -- see the CR-02
|
|
437
|
+
// comment above for the full mechanism); step 7's EXIT is what undoes it.
|
|
436
438
|
await client.send(CommandType.Ping);
|
|
437
439
|
|
|
438
440
|
// Step 4: build identity.
|
package/stock-execution.ts
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
// `handleExecutionUntilReturn` as `StockSessionHandler`s -- dispatch-table
|
|
8
8
|
// and manifest wiring belong to plans 03-12/03-13, not here.
|
|
9
9
|
//
|
|
10
|
-
// WHY THIS FILE EXISTS:
|
|
11
|
-
//
|
|
10
|
+
// WHY THIS FILE EXISTS: ANY inbound byte halts the emulated machine --
|
|
11
|
+
// `monitor_check_binary()` calls `monitor_startup_trap()` on every byte the
|
|
12
|
+
// monitor socket receives (`monitor_binary.c:281`), and that check runs
|
|
13
|
+
// every vsync -- so
|
|
12
14
|
// a bare PING (0x81) is the documented, side-effect-minimal way to trigger a
|
|
13
15
|
// halt on demand, and EXIT (0xaa) is the ONLY thing that resumes it. D-05
|
|
14
16
|
// means this client never sends an unrequested EXIT, which in turn means an
|
package/stock-memory-search.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// stock-memory-search.ts
|
|
3
3
|
//
|
|
4
4
|
// vice_memory_search / vice_memory_compare -- the DERIV-01 pair. Both are
|
|
5
|
-
// DERIVED tools: the binary monitor's confirmed command set
|
|
6
|
-
//
|
|
5
|
+
// DERIVED tools: the binary monitor's confirmed command set -- read directly
|
|
6
|
+
// out of VICE's own monitor_binary.c and reproduced by probing every opcode
|
|
7
|
+
// live against genuine stock VICE -- has no MEMORY_SEARCH or MEMORY_COMPARE
|
|
7
8
|
// opcode at all, so both answers are computed CLIENT-SIDE from one bounded
|
|
8
9
|
// MEM_GET read per range -- the same shape stock-disassemble.ts already
|
|
9
10
|
// uses. Registered through withDerivedTool("...", { needsSession: true },
|
package/stock-protocol.ts
CHANGED
|
@@ -73,10 +73,13 @@ export const MAX_BODY_LEN = 4 * 1024 * 1024;
|
|
|
73
73
|
export const MAX_BUFFERED_LEN = RESPONSE_HEADER_LEN + MAX_BODY_LEN + 64 * 1024;
|
|
74
74
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
|
-
// Command / response / error "enums" -- one-for-one with
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
76
|
+
// Command / response / error "enums" -- one-for-one with the wire format
|
|
77
|
+
// empirically confirmed against genuine stock VICE's binary monitor (every
|
|
78
|
+
// opcode probed live, request and response bytes captured and matched
|
|
79
|
+
// against monitor_binary.c's own encoder). This set is a superset of the
|
|
80
|
+
// vendor fork's own CommandType: it is missing RESOURCE_GET/SET,
|
|
81
|
+
// CPUHISTORY_GET, and USERPORT_SET, which stock supports and the fork's
|
|
82
|
+
// enum simply never grew to cover.
|
|
80
83
|
//
|
|
81
84
|
// Deviation from the plan's literal wording ("plain TypeScript enums, not
|
|
82
85
|
// `const enum`"): a real TypeScript `enum` -- plain or const -- emits
|
|
@@ -375,9 +378,11 @@ export interface EncodeRequestHeaderOptions {
|
|
|
375
378
|
body?: Buffer;
|
|
376
379
|
}
|
|
377
380
|
|
|
378
|
-
/** Build the normative 11-byte binary-monitor request header
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
+
/** Build the normative 11-byte binary-monitor request header -- the exact
|
|
382
|
+
* byte layout read directly out of VICE's own monitor_binary.c request
|
|
383
|
+
* decoder and reproduced on the wire against a genuine stock instance --
|
|
384
|
+
* plus body: STX, api_version, uint32 LE body length, uint32 LE request id,
|
|
385
|
+
* command type byte. */
|
|
381
386
|
export function encodeRequestHeader({ commandType, requestId, body = Buffer.alloc(0) }: EncodeRequestHeaderOptions): Buffer {
|
|
382
387
|
const header = Buffer.alloc(REQUEST_HEADER_LEN);
|
|
383
388
|
header[0] = VICE_STX;
|
|
@@ -403,7 +408,8 @@ export function encodeRequestHeader({ commandType, requestId, body = Buffer.allo
|
|
|
403
408
|
// converted to a TypeScript options-object signature matching
|
|
404
409
|
// encodeRequestHeader()'s own style above. The rest are derived fresh from
|
|
405
410
|
// the official VICE manual (vice-emu.sourceforge.io/vice_13.html §13) and
|
|
406
|
-
//
|
|
411
|
+
// from the byte layout read directly out of VICE's own monitor_binary.c
|
|
412
|
+
// request decoder; every encoder whose runtime BEHAVIOUR
|
|
407
413
|
// (not wire shape) is unconfirmed against a real binary says so explicitly
|
|
408
414
|
// in its own JSDoc as [ASSUMED], naming the RESEARCH.md Assumptions Log
|
|
409
415
|
// row -- never silently claimed as verified.
|
|
@@ -455,7 +461,10 @@ export interface MemspaceBodyOptions {
|
|
|
455
461
|
}
|
|
456
462
|
|
|
457
463
|
/** The one-byte body shared by REGISTERS_GET (0x31) and REGISTERS_AVAILABLE
|
|
458
|
-
* (0x83)
|
|
464
|
+
* (0x83): a single memspace byte, confirmed against monitor_binary.c's own
|
|
465
|
+
* request decoder and by a live probe against genuine stock VICE that sent
|
|
466
|
+
* each memspace value and matched the returned register set to the
|
|
467
|
+
* expected bank. */
|
|
459
468
|
export function memspaceBody({ memspace }: MemspaceBodyOptions = {}): Buffer {
|
|
460
469
|
return Buffer.from([memspaceByte(memspace)]);
|
|
461
470
|
}
|
|
@@ -474,7 +483,8 @@ export interface MemGetBodyOptions {
|
|
|
474
483
|
* MEM_GET (0x01) request body -- ALWAYS EXACTLY 8 BYTES:
|
|
475
484
|
* `sidefx(1) start(u16LE) end(u16LE) memspace(1) bank(u16LE)`.
|
|
476
485
|
* [VERIFIED against probe-binmon.mjs:268-276, this repo's own
|
|
477
|
-
* offline-tested reference
|
|
486
|
+
* offline-tested reference, and against the field layout read directly
|
|
487
|
+
* out of monitor_binary.c's own MEM_GET request decoder]
|
|
478
488
|
*
|
|
479
489
|
* The body is always 8 bytes -- never shorter -- because stock VICE's
|
|
480
490
|
* `monitor_binary.c` handler dereferences every one of these fields before
|
|
@@ -510,7 +520,9 @@ export interface MemSetBodyOptions {
|
|
|
510
520
|
* MEM_SET (0x02) request body -- the same 8-byte header as memGetBody(),
|
|
511
521
|
* with `sidefx` forced to `0x00` (MEM_SET has no side-effect flag on the
|
|
512
522
|
* wire), then `data` appended at offset 8.
|
|
513
|
-
* [VERIFIED probe-binmon.mjs:278-287
|
|
523
|
+
* [VERIFIED probe-binmon.mjs:278-287, and against monitor_binary.c's own
|
|
524
|
+
* MEM_SET request decoder, which reads the identical 8-byte header before
|
|
525
|
+
* the variable-length data that follows it]
|
|
514
526
|
*/
|
|
515
527
|
export function memSetBody({ start, end, memspace, bank = 0x0000, data }: MemSetBodyOptions): Buffer {
|
|
516
528
|
requireU16("start", start);
|
|
@@ -540,7 +552,9 @@ export function memSetBody({ start, end, memspace, bank = 0x0000, data }: MemSet
|
|
|
540
552
|
* CHECKPOINT_DELETE (0x13). Not an options object (matching
|
|
541
553
|
* probe-binmon.mjs:314-318's own bare-number signature) since it has
|
|
542
554
|
* nothing else to validate offline.
|
|
543
|
-
* [VERIFIED probe-binmon.mjs:314-318
|
|
555
|
+
* [VERIFIED probe-binmon.mjs:314-318, and against monitor_binary.c's own
|
|
556
|
+
* CHECKPOINT_GET/CHECKPOINT_DELETE request decoder, which reads exactly
|
|
557
|
+
* this 4-byte checkpoint number and nothing else]
|
|
544
558
|
*/
|
|
545
559
|
export function cpNumBody(checkpointNum: number): Buffer {
|
|
546
560
|
requireU32("checkpointNum", checkpointNum);
|
|
@@ -568,7 +582,9 @@ export interface CheckpointSetBodyOptions {
|
|
|
568
582
|
* CHECKPOINT_SET (0x12) request body -- 8 bytes, or 9 when `memspace` is
|
|
569
583
|
* supplied: `start(u16LE) end(u16LE) stop(1) enabled(1) operation(1)
|
|
570
584
|
* temporary(1) [memspace(1)]`.
|
|
571
|
-
* [VERIFIED probe-binmon.mjs:290-309
|
|
585
|
+
* [VERIFIED probe-binmon.mjs:290-309, and against monitor_binary.c's own
|
|
586
|
+
* CHECKPOINT_SET request decoder, which only reads the ninth memspace byte
|
|
587
|
+
* when the declared body length says it is present]
|
|
572
588
|
*/
|
|
573
589
|
export function checkpointSetBody({
|
|
574
590
|
start,
|
|
@@ -606,7 +622,9 @@ export interface CheckpointToggleBodyOptions {
|
|
|
606
622
|
}
|
|
607
623
|
|
|
608
624
|
/** CHECKPOINT_TOGGLE (0x15) request body -- 5 bytes,
|
|
609
|
-
* `checkpointNum(u32LE) enabled(1)
|
|
625
|
+
* `checkpointNum(u32LE) enabled(1)`: read directly out of monitor_binary.c's
|
|
626
|
+
* own CHECKPOINT_TOGGLE request decoder, which reads exactly these five
|
|
627
|
+
* bytes and nothing else. */
|
|
610
628
|
export function checkpointToggleBody({ checkpointNum, enabled }: CheckpointToggleBodyOptions): Buffer {
|
|
611
629
|
requireU32("checkpointNum", checkpointNum);
|
|
612
630
|
const body = Buffer.alloc(5);
|
|
@@ -624,7 +642,9 @@ export interface ConditionSetBodyOptions {
|
|
|
624
642
|
* CONDITION_SET (0x22) request body -- `checkpointNum(u32LE) exprLen(1)
|
|
625
643
|
* expr(ASCII, NOT NUL-terminated)`.
|
|
626
644
|
* [VERIFIED probe-binmon.mjs:320-332, including its own >255-byte guard,
|
|
627
|
-
* ported verbatim
|
|
645
|
+
* ported verbatim, and against monitor_binary.c's own CONDITION_SET request
|
|
646
|
+
* decoder, which reads exprLen as a single uint8 immediately before the
|
|
647
|
+
* expression bytes]
|
|
628
648
|
*
|
|
629
649
|
* This is the ONLY function in this tree that ever turns condition TEXT
|
|
630
650
|
* into wire bytes -- its `expression` argument must always come from
|
|
@@ -685,7 +705,8 @@ export interface RegistersSetBodyOptions {
|
|
|
685
705
|
* REGISTERS_SET (0x32) request body -- `memspace(1) count(u16LE)` then per
|
|
686
706
|
* item `itemSize(1) regId(1) value(u16LE)`, with `itemSize` always `3`
|
|
687
707
|
* (the byte count following the itemSize byte itself: 1 id byte + 2 value
|
|
688
|
-
* bytes)
|
|
708
|
+
* bytes), matching the stride monitor_binary.c's own REGISTERS_SET request
|
|
709
|
+
* decoder walks each item with.
|
|
689
710
|
*
|
|
690
711
|
* This is the structural inverse of this file's `ResponseType.RegisterInfo`
|
|
691
712
|
* parser case (below, in the response-parsing section -- grep `case
|
|
@@ -738,8 +759,11 @@ export interface AdvanceInstructionsBodyOptions {
|
|
|
738
759
|
|
|
739
760
|
/**
|
|
740
761
|
* ADVANCE_INSTRUCTIONS (0x71) request body -- 3 bytes, `stepOver(1)
|
|
741
|
-
* count(u16LE)
|
|
742
|
-
*
|
|
762
|
+
* count(u16LE)`, confirmed byte-for-byte against monitor_binary.c's own
|
|
763
|
+
* decoder and, for the stepOver=0 case, exercised live against genuine
|
|
764
|
+
* stock VICE by probe-binmon.mjs's async-events check (the check that
|
|
765
|
+
* proved STOPPED/RESUMED events arrive interleaved with the command
|
|
766
|
+
* response rather than only after it).
|
|
743
767
|
*
|
|
744
768
|
* `stepOver = true`'s runtime meaning (skip a `JSR`'s subroutine as one
|
|
745
769
|
* step, matching the fork's own `stepOver` field name) was live-probed
|
|
@@ -767,8 +791,10 @@ export interface KeyboardFeedBodyOptions {
|
|
|
767
791
|
}
|
|
768
792
|
|
|
769
793
|
/**
|
|
770
|
-
* KEYBOARD_FEED (0x72) request body -- `textLen(1) text(bytes)
|
|
771
|
-
*
|
|
794
|
+
* KEYBOARD_FEED (0x72) request body -- `textLen(1) text(bytes)`, read
|
|
795
|
+
* directly out of monitor_binary.c's own KEYBOARD_FEED request decoder,
|
|
796
|
+
* which takes a one-byte length prefix followed by exactly that many raw
|
|
797
|
+
* bytes -- no PETSCII conversion happens on the wire side.
|
|
772
798
|
*/
|
|
773
799
|
export function keyboardFeedBody({ petscii }: KeyboardFeedBodyOptions): Buffer {
|
|
774
800
|
if (petscii.length === 0) {
|
|
@@ -789,8 +815,8 @@ export interface JoyportSetBodyOptions {
|
|
|
789
815
|
}
|
|
790
816
|
|
|
791
817
|
/**
|
|
792
|
-
* JOYPORT_SET (0xa2) request body -- 4 bytes, `port(u16LE) value(u16LE)
|
|
793
|
-
*
|
|
818
|
+
* JOYPORT_SET (0xa2) request body -- 4 bytes, `port(u16LE) value(u16LE)`,
|
|
819
|
+
* read directly out of monitor_binary.c's own JOYPORT_SET request decoder.
|
|
794
820
|
*
|
|
795
821
|
* The body SHAPE is cited; the BIT MEANING of `value` (which bit is
|
|
796
822
|
* up/down/left/right/fire) is [ASSUMED] -- RESEARCH.md Assumptions Log row
|
|
@@ -817,8 +843,9 @@ export interface ResetBodyOptions {
|
|
|
817
843
|
}
|
|
818
844
|
|
|
819
845
|
/**
|
|
820
|
-
* RESET (0xcc) request body -- 1 byte, `resetMode
|
|
821
|
-
*
|
|
846
|
+
* RESET (0xcc) request body -- 1 byte, `resetMode`, read directly out of
|
|
847
|
+
* monitor_binary.c's own RESET request decoder, which reads exactly this
|
|
848
|
+
* one byte and nothing else.
|
|
822
849
|
*
|
|
823
850
|
* NOT the RESOURCE_SET (0x52) power-cycle hazard CLAUDE.md warns about
|
|
824
851
|
* (`MachineVideoStandard`/`VICIIModel`/`MachinePowerFrequency`, the CUT
|
|
@@ -845,7 +872,9 @@ export interface AutostartBodyOptions {
|
|
|
845
872
|
|
|
846
873
|
/**
|
|
847
874
|
* AUTOSTART (0xdd) request body -- `runAfter(1) fileIndex(u16LE)
|
|
848
|
-
* filenameLen(1) filename(ASCII)
|
|
875
|
+
* filenameLen(1) filename(ASCII)`, read directly out of monitor_binary.c's
|
|
876
|
+
* own AUTOSTART request decoder, which reads the filename length as the
|
|
877
|
+
* single byte immediately following the fixed runAfter/fileIndex fields.
|
|
849
878
|
*
|
|
850
879
|
* AUTOSTART has NO drive-unit field at all -- a caller cannot target units
|
|
851
880
|
* 9-11 through this opcode (plan 03-10 owns the refusal for those units).
|
|
@@ -871,7 +900,9 @@ export interface DumpBodyOptions {
|
|
|
871
900
|
|
|
872
901
|
/**
|
|
873
902
|
* DUMP (0x41) request body -- `saveRoms(1) saveDisks(1) filenameLen(1)
|
|
874
|
-
* filename(ASCII)
|
|
903
|
+
* filename(ASCII)`, read directly out of monitor_binary.c's own DUMP
|
|
904
|
+
* request decoder, which reads the two flag bytes before the filename
|
|
905
|
+
* length prefix.
|
|
875
906
|
*/
|
|
876
907
|
export function dumpBody({ saveRoms, saveDisks, filename }: DumpBodyOptions): Buffer {
|
|
877
908
|
const filenameBuf = requireAsciiFilename("dumpBody", filename);
|
|
@@ -887,8 +918,9 @@ export interface UndumpBodyOptions {
|
|
|
887
918
|
filename: string;
|
|
888
919
|
}
|
|
889
920
|
|
|
890
|
-
/** UNDUMP (0x42) request body -- `filenameLen(1) filename(ASCII)
|
|
891
|
-
*
|
|
921
|
+
/** UNDUMP (0x42) request body -- `filenameLen(1) filename(ASCII)`, read
|
|
922
|
+
* directly out of monitor_binary.c's own UNDUMP request decoder, which
|
|
923
|
+
* reads the filename length as the frame's very first byte. */
|
|
892
924
|
export function undumpBody({ filename }: UndumpBodyOptions): Buffer {
|
|
893
925
|
const filenameBuf = requireAsciiFilename("undumpBody", filename);
|
|
894
926
|
const body = Buffer.alloc(1 + filenameBuf.length);
|
package/stock-registers.ts
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
// vice_registers_set takes a register NAME (D-03: stock keeps the fork's
|
|
13
13
|
// argument shape). VICE enumerates its own register ids through
|
|
14
14
|
// REGISTERS_AVAILABLE, and those ids are NOT guaranteed identical across
|
|
15
|
-
// builds
|
|
15
|
+
// builds -- confirmed by reading VICE's own mon_register.c, which assigns
|
|
16
|
+
// ids from a per-machine table that is never pinned to a fixed numbering
|
|
17
|
+
// across ports -- so a caller must resolve a
|
|
16
18
|
// name through the CONNECTED build's own answer, never a table this
|
|
17
19
|
// module wrote down in advance. This file is the one seam that performs
|
|
18
20
|
// that resolution, caches it per session (so every call after the first
|
package/text-protocol.ts
CHANGED
|
@@ -301,9 +301,12 @@ export const HAZARD_SUBJECT_PRG_RELPATHS = Object.freeze({
|
|
|
301
301
|
* `exportAsmTree()` itself emitted (`scope_087a.a`) rather than in the
|
|
302
302
|
* hand-written `modified` fixture family, reassembled through the same
|
|
303
303
|
* single oracle against a pre-registered byte manifest committed at
|
|
304
|
-
* `fixtures/hazard-subject/exported-edit.manifest.json`.
|
|
305
|
-
*
|
|
306
|
-
*
|
|
304
|
+
* `fixtures/hazard-subject/exported-edit.manifest.json`. Both the
|
|
305
|
+
* rebuild and the live-behaviour gate came back acknowledged: the
|
|
306
|
+
* reassembly reproduced the manifest byte-for-byte and both behaviour
|
|
307
|
+
* changes were independently confirmed to take effect in a running
|
|
308
|
+
* genuine stock VICE session via checkpoint-driven RAM captures, not
|
|
309
|
+
* just in the rebuilt bytes on disk. */
|
|
307
310
|
"exported-edit": Object.freeze([
|
|
308
311
|
"src",
|
|
309
312
|
"mcp",
|
package/textmon-profile.ts
CHANGED
|
@@ -142,13 +142,18 @@ const RULES_LINE = "------------- ------ ------------- ------";
|
|
|
142
142
|
|
|
143
143
|
/** VICE's own cold-profiler sentence, quoted byte-for-byte -- including its
|
|
144
144
|
* embedded double quotes and its trailing period -- from
|
|
145
|
-
* `text-protocol.ts`'s `TEXT_COMMAND_ALLOWLIST` doc comment
|
|
146
|
-
* `docs/phase42-text-format-drift-citations.md`'s Block 9. Unlike this
|
|
145
|
+
* `text-protocol.ts`'s `TEXT_COMMAND_ALLOWLIST` doc comment. Unlike this
|
|
147
146
|
* module's siblings' source-traced strings, this one is MEASURED: observed
|
|
148
147
|
* live against genuine stock `x64sc (VICE 3.9)`, 2026-09-09. `prof flat`
|
|
149
148
|
* alone, on a freshly connected session that has never issued `prof on`,
|
|
150
149
|
* returns exactly this sentence -- the profiler subsystem is compiled in
|
|
151
|
-
* and the command itself is fine, it simply has nothing recorded yet.
|
|
150
|
+
* and the command itself is fine, it simply has nothing recorded yet. This
|
|
151
|
+
* was a genuinely new live finding, present in neither committed fixture:
|
|
152
|
+
* VICE's flat profiler defaults OFF, and no production handler in this
|
|
153
|
+
* tree issues `prof on` before dialing `prof flat`, so `vice_profile_flat`
|
|
154
|
+
* as shipped cannot yet produce real profile rows against a freshly
|
|
155
|
+
* launched instance -- a real, separately-tracked gap this live run
|
|
156
|
+
* surfaced rather than silently absorbed. */
|
|
152
157
|
export const PROFILING_NOT_STARTED_TEXT = 'No profiling data available. Start profiling with "prof on".';
|
|
153
158
|
|
|
154
159
|
/** The narrow no-break space (U+202F) VICE uses as its thousands separator
|