@henols/vice-mcp 0.2.1 → 0.2.2
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/README.md +2 -1
- package/THIRD-PARTY-NOTICES.md +1 -24
- package/{r2000-acme-ident.ts → anno-acme-ident.ts} +13 -13
- package/anno-cli.ts +1465 -0
- package/{r2000-confidence.ts → anno-confidence.ts} +22 -22
- package/anno-coverage.ts +2465 -0
- package/{r2000-d64.ts → anno-d64.ts} +5 -5
- package/anno-derive.ts +590 -0
- package/anno-details.ts +169 -0
- package/anno-enum-gen.ts +533 -0
- package/anno-export-asm.ts +1310 -0
- package/anno-index.ts +150 -0
- package/{r2000-memmap-render.ts → anno-memmap-render.ts} +236 -95
- package/{r2000-regbits-gen.ts → anno-regbits-gen.ts} +20 -15
- package/{r2000-regbits.json → anno-regbits.json} +2 -2
- package/anno-register.ts +240 -0
- package/anno-store.ts +3486 -0
- package/anno-symbols.ts +266 -0
- package/anno-tools.ts +2111 -0
- package/anno-types.ts +1636 -0
- package/block-class.ts +201 -0
- package/build.ts +1 -1
- package/capability-registry.ts +3 -1
- package/disasm-decoder.ts +14 -14
- package/disasm-opcodes.ts +4 -4
- package/disasm-renderer.ts +2 -2
- package/hostpath.ts +1 -1
- package/install-resources.ts +1 -1
- package/package.json +23 -17
- package/prg-image.ts +119 -0
- package/repo-root.ts +20 -5
- package/resources/broker-launch.mjs +8 -4
- package/resources/vice-launcher.sh +3 -3
- package/stock-address.ts +5 -5
- package/stock-cia.ts +2 -2
- package/stock-condition.ts +7 -7
- package/stock-connect.ts +1 -1
- package/stock-dispatch.ts +35 -5
- package/stock-execution.ts +5 -3
- package/stock-input.ts +9 -9
- package/stock-machine.ts +17 -6
- package/stock-protocol.ts +16 -11
- package/stock-registers.ts +54 -29
- package/stock-sprites.ts +3 -3
- package/stock-symbols.ts +9 -9
- package/stock-timing.ts +1 -1
- package/stock-vicii.ts +1 -1
- package/version.ts +1 -1
- package/vice-proxy.ts +68 -46
- package/r2000-cli.ts +0 -1103
- package/r2000-enum-gen.ts +0 -574
- package/r2000-launch.ts +0 -357
- package/r2000-mcp-client.ts +0 -596
- package/r2000-project.ts +0 -190
- package/r2000-symbols.ts +0 -388
- package/r2000-tools.ts +0 -914
- package/r2000-verify.ts +0 -184
package/stock-address.ts
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
// hex by default" rule belongs to the checkpoint-condition emitter, not
|
|
24
24
|
// this parser. Do not conflate the two.
|
|
25
25
|
// - Never implement symbol resolution in Phase 3. setSymbolResolver() is
|
|
26
|
-
//
|
|
27
|
-
//
|
|
26
|
+
// the one extension point for it: it starts `null` and stays that way
|
|
27
|
+
// until stock-symbols.ts's `vice_symbols_load` installs a real resolver.
|
|
28
28
|
// - Never add a second resolver holder. `nameFor` (address -> name,
|
|
29
29
|
// DISASM-06's first consumer, Phase 4) and `resolve` (name -> address,
|
|
30
30
|
// Phase 3) live on the SAME `SymbolResolver` object, read from the SAME
|
|
@@ -46,9 +46,9 @@ export interface SymbolResolver {
|
|
|
46
46
|
// -- no symbol resolution happens until a later phase installs one.
|
|
47
47
|
let symbolResolver: SymbolResolver | null = null;
|
|
48
48
|
|
|
49
|
-
/** The
|
|
50
|
-
*
|
|
51
|
-
* loaded" refusal. */
|
|
49
|
+
/** The extension point stock-symbols.ts's `vice_symbols_load` fills at
|
|
50
|
+
* runtime. Passing `null` (the default before any load) restores the "no
|
|
51
|
+
* symbol table loaded" refusal. */
|
|
52
52
|
export function setSymbolResolver(resolver: SymbolResolver | null): void {
|
|
53
53
|
symbolResolver = resolver;
|
|
54
54
|
}
|
package/stock-cia.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// field naming the sharing address.
|
|
24
24
|
//
|
|
25
25
|
// Bit-field names below were transcribed ONCE from
|
|
26
|
-
//
|
|
26
|
+
// `src/skills/c64-memory-mapping/memmap.json`'s entries for $DC00,
|
|
27
27
|
// $DC01, $DC02, $DC03, $DC08-$DC0F, $DD00, $DD01 and $DD0D, and
|
|
28
28
|
// cross-checked at write time -- the same "committed literal, cross-checked
|
|
29
29
|
// once, no automated drift check" posture Phase 4's D-06 already accepted
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
// - This is NOT a keyboard-matrix read. $DC00/$DC01 expose only the
|
|
37
37
|
// current column selection and row result; the full matrix is
|
|
38
38
|
// `vice_keyboard_matrix`, which is provably unrecoverable on stock
|
|
39
|
-
// (`docs/stock-vice-parity.md` SS A item 2) and
|
|
39
|
+
// (`docs/stock-vice-parity.md` SS A item 2) -- the wire protocol has no matrix command, and `KEYBOARD_FEED` (0x72) injects buffer text only.
|
|
40
40
|
// - WR-02 (2026-08-17): the port A/B joystick bits share their PINS with
|
|
41
41
|
// the keyboard matrix's column-select ($DC00) and row-read ($DC01), and
|
|
42
42
|
// a stock read halts the machine at an arbitrary PC -- often inside the
|
package/stock-condition.ts
CHANGED
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
// last gate before the wire and re-validates every literal and every
|
|
36
36
|
// kind itself, even though parseConditionString() and conditionFromJson()
|
|
37
37
|
// also validate on the way in.
|
|
38
|
-
// -
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
38
|
+
// - GAIN-06's raster-semantics extension (finer-grained raster/cycle
|
|
39
|
+
// conditions) was cut with the whole of Stock-Only Gains, 2026-08-17
|
|
40
|
+
// (see docs/stock-vice-parity.md's dated cut record). Widening this AST
|
|
41
|
+
// is unclaimed scope with no owner -- grow these types, never fork them.
|
|
42
42
|
//
|
|
43
43
|
// This module has no handlers and no dispatch entries -- a later plan
|
|
44
44
|
// consumes emitCondition()'s output as the only thing a condition-set
|
|
@@ -545,9 +545,9 @@ function parseSingleComparison(text: string, originalExpr: string): ConditionNod
|
|
|
545
545
|
* conditionFromJson() produces and returns it; it never emits text itself,
|
|
546
546
|
* so emitCondition() remains the only producer of wire text.
|
|
547
547
|
*
|
|
548
|
-
* Accepted input, deliberately narrow (
|
|
549
|
-
*
|
|
550
|
-
* expression parser):
|
|
548
|
+
* Accepted input, deliberately narrow (GAIN-06's plan to widen this grammar
|
|
549
|
+
* was cut along with Stock-Only Gains, 2026-08-17; widening it is unclaimed
|
|
550
|
+
* scope with no owner -- do not implement a general expression parser):
|
|
551
551
|
* - a single comparison, with or without surrounding parentheses:
|
|
552
552
|
* "A == $42", "(PC == $c000)", "SP <= $ff", "RL == $64"
|
|
553
553
|
* - a conjunction/disjunction where EVERY comparison is individually
|
package/stock-connect.ts
CHANGED
|
@@ -534,7 +534,7 @@ export async function stockReconnect(session: StockConnectSession, { lastToolCal
|
|
|
534
534
|
|
|
535
535
|
if (baselineEpoch === null || currentEpoch === null || currentEpoch !== baselineEpoch) {
|
|
536
536
|
throw new MachineRestartedError(
|
|
537
|
-
`
|
|
537
|
+
`stockReconnect: reconnect to target ${session.targetId} could not prove machine identity across the reconnect ` +
|
|
538
538
|
`(baseline epoch ${String(baselineEpoch)}, current epoch ${String(currentEpoch)})`,
|
|
539
539
|
{ baselineEpoch, currentEpoch, where: "stock-connect.ts:stockReconnect", lastToolCall },
|
|
540
540
|
);
|
package/stock-dispatch.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { resolve, join } from "node:path";
|
|
|
28
28
|
|
|
29
29
|
import type { ViceBackend } from "./backend-detect.mts";
|
|
30
30
|
import type { ToolInfo } from "./vice.ts";
|
|
31
|
+
import { capabilityRefusalMessage } from "./capability-registry.ts";
|
|
31
32
|
import { type HeldLease } from "./vice-broker-client.ts";
|
|
32
33
|
import { stockConnect, stockDisconnect, stockReconnect, type StockConnectSession, type StockConnectDeps } from "./stock-connect.ts";
|
|
33
34
|
import {
|
|
@@ -591,7 +592,22 @@ export function withDerivedTool(
|
|
|
591
592
|
* comment on why). Built through stockAnswer() so the answer now also
|
|
592
593
|
* carries `runState` (D-06: every stock answer, and `vice_ping` is a stock
|
|
593
594
|
* answer) alongside every field that was already there.
|
|
595
|
+
*
|
|
596
|
+
* 2026-08-19 finding (closed Phase 15 plan 15-09): `resolvedBinaryPath` is a
|
|
597
|
+
* ONE-TIME, MCP-server-process-startup `$PATH` probe (see vice-proxy.ts's
|
|
598
|
+
* `ACTIVE_BACKEND` comment) -- it is independent of which binary the broker
|
|
599
|
+
* actually leased for THIS request. `resolvedBinaryPathScope` below is an
|
|
600
|
+
* additive, backward-compatible sibling field (the existing field name and
|
|
601
|
+
* shape are unchanged) that carries that qualification into the answer
|
|
602
|
+
* itself, so a caller reading the response -- not just this source comment --
|
|
603
|
+
* learns not to treat the path as this request's authoritative binary
|
|
604
|
+
* identity.
|
|
594
605
|
*/
|
|
606
|
+
const RESOLVED_BINARY_PATH_SCOPE =
|
|
607
|
+
"one-time MCP-server-process-startup PATH probe; NOT the binary the broker leased for this " +
|
|
608
|
+
"request -- for the authoritative per-instance binary, read the broker's own launch record " +
|
|
609
|
+
"(epoch.json's vice_bin field)";
|
|
610
|
+
|
|
595
611
|
const handlePing: StockSessionHandler = async (_args, session, deps) => {
|
|
596
612
|
return stockAnswer(session.client, {
|
|
597
613
|
status: "ok",
|
|
@@ -602,6 +618,9 @@ const handlePing: StockSessionHandler = async (_args, session, deps) => {
|
|
|
602
618
|
// it, an agent reading `"x64sc"` cannot tell "this is where the binary is"
|
|
603
619
|
// from "this is what we were told to look for, and we could not find it".
|
|
604
620
|
resolvedBinaryPathIsResolved: deps.resolvedBinaryPathIsResolved ?? false,
|
|
621
|
+
// 2026-08-19 finding: names what resolvedBinaryPath actually is (a
|
|
622
|
+
// startup-time probe) and where to look instead for a per-request answer.
|
|
623
|
+
resolvedBinaryPathScope: RESOLVED_BINARY_PATH_SCOPE,
|
|
605
624
|
capabilities: session.capabilities,
|
|
606
625
|
});
|
|
607
626
|
};
|
|
@@ -611,11 +630,11 @@ const handlePing: StockSessionHandler = async (_args, session, deps) => {
|
|
|
611
630
|
* not an oversight (Task 2, plan 03-12):
|
|
612
631
|
* - `vice_checkpoint_set_ignore_count` (D-15)
|
|
613
632
|
* - `vice_snapshot_list` (D-16 -- deleted from both manifests)
|
|
614
|
-
* - `vice_disk_detach` (
|
|
615
|
-
* - `vice_joystick_tap` (
|
|
633
|
+
* - `vice_disk_detach` (CUT from scope 2026-08-17 -- docs/stock-vice-parity.md's dated cut record)
|
|
634
|
+
* - `vice_joystick_tap` (permanently excluded -- stock-input.ts's own header)
|
|
616
635
|
* - `vice_disk_read_sector` (CUT from scope 2026-08-17 -- no skill calls it; see ROADMAP.md "Cut from scope (v0.2.0, 2026-08-17)" and docs/stock-vice-parity.md item 6)
|
|
617
636
|
* - `vice_sid_get_state` and the low-level keyboard family (hard losses)
|
|
618
|
-
* - `vice_machine_config_get` / `vice_machine_config_set` (
|
|
637
|
+
* - `vice_machine_config_get` / `vice_machine_config_set` (CUT from scope 2026-08-17 along with the whole of Stock-Only Gains -- docs/stock-vice-parity.md's dated cut record)
|
|
619
638
|
* `dispatchStock()`'s miss branch already refuses any of these by name,
|
|
620
639
|
* without reading `deps` -- there is nothing else to add for them here.
|
|
621
640
|
*/
|
|
@@ -732,9 +751,20 @@ export function stockHandlerFor(name: string): StockHandler | undefined {
|
|
|
732
751
|
export async function dispatchStock(name: string, args: Record<string, unknown>, deps: StockDispatchDeps): Promise<StockToolResult> {
|
|
733
752
|
const handler = stockHandlerFor(name);
|
|
734
753
|
if (!handler) {
|
|
754
|
+
// WR-13: route through capability-registry.ts's ONE authoritative
|
|
755
|
+
// refusal renderer rather than a second, locally-composed wording --
|
|
756
|
+
// that renderer knows which backend ACTUALLY provides each name
|
|
757
|
+
// (avoiding the false "the fork backend provides this tool" claim for a
|
|
758
|
+
// stock-only-gain name) and never uses "wait for a later phase" framing
|
|
759
|
+
// for a hardware loss. Fall back to an internal-inconsistency message
|
|
760
|
+
// ONLY when the registry has no entry at all for `name` -- meaning the
|
|
761
|
+
// tool is advertised on the stock manifest (so it reached this branch)
|
|
762
|
+
// but stockHandlerFor() has no dispatch entry AND capability-registry.ts
|
|
763
|
+
// has no divergence entry either: a bug to report, not a capability gap.
|
|
735
764
|
return isErrorText(
|
|
736
|
-
|
|
737
|
-
|
|
765
|
+
capabilityRefusalMessage(name, "stock") ??
|
|
766
|
+
`${name} is advertised on the stock backend's manifest but has no handler in the stock ` +
|
|
767
|
+
`dispatch table -- this is an internal inconsistency, not a capability gap; please file an issue.`,
|
|
738
768
|
);
|
|
739
769
|
}
|
|
740
770
|
return handler(args, deps);
|
package/stock-execution.ts
CHANGED
|
@@ -224,9 +224,11 @@ export const handleExecutionRun: StockSessionHandler = async (args, session) =>
|
|
|
224
224
|
* the derived run state is "unknown" (D-07, via refuseIfUnknown()).
|
|
225
225
|
*
|
|
226
226
|
* `stepOver: true`'s runtime semantic (skip a JSR's subroutine as one step)
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
227
|
+
* was live-probed against a real JSR on fork VICE 3.10 and CONFIRMED
|
|
228
|
+
* (RESEARCH.md Assumptions Log row A2, closed by 13-PROBE-RESULTS.md §A2):
|
|
229
|
+
* the post-step PC landed at JSR+3, matching a full subroutine skip, and
|
|
230
|
+
* the result was reproduced identically across two independent live
|
|
231
|
+
* sessions.
|
|
230
232
|
*/
|
|
231
233
|
export const handleExecutionStep: StockSessionHandler = async (args, session) => {
|
|
232
234
|
const unexpected = refuseUnexpectedArgs(args, ["count", "stepOver"], "vice_execution_step");
|
package/stock-input.ts
CHANGED
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
// resumes explicitly, on its own schedule.
|
|
26
26
|
// - Never add vice_joystick_tap. A tap needs the machine to RUN for a
|
|
27
27
|
// measured interval -- an unrequested EXIT (forbidden by D-05) plus a
|
|
28
|
-
// frame/cycle measurement
|
|
29
|
-
//
|
|
30
|
-
// vice_joystick_set (hold/release/centre)
|
|
31
|
-
// joystick half
|
|
28
|
+
// frame/cycle measurement stock's wire protocol has no route for at
|
|
29
|
+
// all (docs/stock-vice-parity.md section A item 7). This exclusion is
|
|
30
|
+
// permanent, not pending: vice_joystick_set (hold/release/centre)
|
|
31
|
+
// satisfies DIRECT-07's joystick half instead.
|
|
32
32
|
// - Never construct an ok-answer outside stockAnswer(). Every successful
|
|
33
33
|
// result below is built through it, never a bare
|
|
34
34
|
// `{ content: [...], isError: false }` literal.
|
|
@@ -187,11 +187,11 @@ function isValidDirection(value: string): value is JoystickDirection {
|
|
|
187
187
|
* the fork's exact names and documented string-or-array shape.
|
|
188
188
|
*
|
|
189
189
|
* vice_joystick_tap is deliberately absent from this module (and the whole
|
|
190
|
-
* stock manifest) -- a tap needs the machine to run for a
|
|
191
|
-
* interval, which is an unrequested EXIT (forbidden by D-05) plus
|
|
192
|
-
* frame/cycle measurement stock
|
|
193
|
-
*
|
|
194
|
-
*
|
|
190
|
+
* stock manifest), permanently -- a tap needs the machine to run for a
|
|
191
|
+
* measured interval, which is an unrequested EXIT (forbidden by D-05) plus
|
|
192
|
+
* a frame/cycle measurement stock's wire protocol has no route for at all
|
|
193
|
+
* (docs/stock-vice-parity.md section A item 7). Do not approximate it with
|
|
194
|
+
* a sleep; do not implement it here.
|
|
195
195
|
*/
|
|
196
196
|
export const handleJoystickSet: StockSessionHandler = async (args, session) => {
|
|
197
197
|
if (!isPlainObject(args)) {
|
package/stock-machine.ts
CHANGED
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
// WHAT NOT TO DO:
|
|
13
13
|
// - Never gate or deny vice_machine_reset's hard mode. CLAUDE.md's
|
|
14
14
|
// power-cycle warning is about RESOURCE_SET (0x52) writes to
|
|
15
|
-
// MachineVideoStandard/VICIIModel/MachinePowerFrequency --
|
|
16
|
-
//
|
|
15
|
+
// MachineVideoStandard/VICIIModel/MachinePowerFrequency -- the CUT
|
|
16
|
+
// vice_machine_config_get/set pair's resources (docs/stock-vice-parity.md), a DIFFERENT opcode entirely. RESET (0xcc) is a distinct
|
|
17
17
|
// command, and an agent-requested hard reset via RESET is exactly what
|
|
18
18
|
// DIRECT-06 asks for. It needs no deny-list (RESEARCH.md Pitfall 1).
|
|
19
19
|
// - Never look for a per-unit disk-attach route mid-implementation.
|
|
20
20
|
// AUTOSTART (0xdd) has NO drive-unit field on the wire at all -- this is
|
|
21
21
|
// a protocol gap, not a code bug you can fix by looking harder
|
|
22
22
|
// (RESEARCH.md Pitfall 2).
|
|
23
|
-
// - Never add a disk-detach handler here. D-13
|
|
24
|
-
//
|
|
25
|
-
// in this file's own acceptance criteria.
|
|
23
|
+
// - Never add a disk-detach handler here. D-13's vice_disk_detach was
|
|
24
|
+
// CUT from scope 2026-08-17 (docs/stock-vice-parity.md) -- grep-gated
|
|
25
|
+
// to zero occurrences of its name in this file's own acceptance criteria.
|
|
26
26
|
// - Never build a host path outside stock-paths.ts. Every filename this
|
|
27
27
|
// file sends through the wire goes through that same one wrapper --
|
|
28
28
|
// grep-gated to zero direct hostPath()/hostPathCandidates() calls here.
|
|
@@ -150,7 +150,18 @@ export const handleAutostart: StockSessionHandler = async (args, session) => {
|
|
|
150
150
|
* at all, so an agent told "attached to unit 9" when the image landed on
|
|
151
151
|
* unit 8 would debug the wrong drive. See docs/stock-vice-parity.md's D-14
|
|
152
152
|
* entry.
|
|
153
|
+
*
|
|
154
|
+
* The returned `approximation` string names BOTH real side effects Phase 13
|
|
155
|
+
* plan 13-03's live A5 probe observed against real fork VICE 3.10: a full
|
|
156
|
+
* machine reset AND a program load, not "attach without disturbing machine
|
|
157
|
+
* state." Exported so the pinning test derives its expectation from this
|
|
158
|
+
* constant rather than re-typing the sentence, so the two cannot drift. See
|
|
159
|
+
* `.planning/phases/13-external-verification/13-PROBE-RESULTS.md` § A5.
|
|
153
160
|
*/
|
|
161
|
+
export const DISK_ATTACH_APPROXIMATION =
|
|
162
|
+
"AUTOSTART (D-14): performs a full machine reset and loads a program from the image; " +
|
|
163
|
+
"unlike vice_autostart it does not issue a final run step, as far as observed.";
|
|
164
|
+
|
|
154
165
|
export const handleDiskAttach: StockSessionHandler = async (args, session) => {
|
|
155
166
|
const a = isPlainObject(args) ? args : {};
|
|
156
167
|
|
|
@@ -182,7 +193,7 @@ export const handleDiskAttach: StockSessionHandler = async (args, session) => {
|
|
|
182
193
|
unit: 8,
|
|
183
194
|
path: containerPath,
|
|
184
195
|
sentPath,
|
|
185
|
-
approximation:
|
|
196
|
+
approximation: DISK_ATTACH_APPROXIMATION,
|
|
186
197
|
});
|
|
187
198
|
} catch (err) {
|
|
188
199
|
return convertWireError("vice_disk_attach", err);
|
package/stock-protocol.ts
CHANGED
|
@@ -720,12 +720,15 @@ export function registersSetBody({ memspace, items }: RegistersSetBodyOptions):
|
|
|
720
720
|
|
|
721
721
|
// ---------------------------------------------------------------------------
|
|
722
722
|
// Execution and machine-control body encoders (Phase 3, Task 2). Every
|
|
723
|
-
// body layout below is [CITED] against the official VICE manual (§13)
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
//
|
|
723
|
+
// body layout below is [CITED] against the official VICE manual (§13);
|
|
724
|
+
// each encoder's own JSDoc states whether its behavioural (not wire-shape)
|
|
725
|
+
// claim has since been live-probed. RESEARCH.md Assumptions Log row A2 was
|
|
726
|
+
// closed CONFIRMED by 13-PROBE-RESULTS.md. Two rows below are still
|
|
727
|
+
// unconfirmed -- A3 stayed INCONCLUSIVE (no observable signal either way)
|
|
728
|
+
// and A5 was CONTRADICTED (still pending its correction) -- and each of
|
|
729
|
+
// those two encoders' own JSDoc, below, still carries its own [ASSUMED]
|
|
730
|
+
// naming that row and pointing at .planning/todos/pending/ for the
|
|
731
|
+
// remaining probe debt.
|
|
729
732
|
// ---------------------------------------------------------------------------
|
|
730
733
|
|
|
731
734
|
export interface AdvanceInstructionsBodyOptions {
|
|
@@ -739,9 +742,11 @@ export interface AdvanceInstructionsBodyOptions {
|
|
|
739
742
|
* exercised, with stepOver=0 only, in probe-binmon.mjs's async-events check]
|
|
740
743
|
*
|
|
741
744
|
* `stepOver = true`'s runtime meaning (skip a `JSR`'s subroutine as one
|
|
742
|
-
* step, matching the fork's own `stepOver` field name)
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
+
* step, matching the fork's own `stepOver` field name) was live-probed
|
|
746
|
+
* against a real `JSR` on fork VICE 3.10 and CONFIRMED (RESEARCH.md
|
|
747
|
+
* Assumptions Log row A2, closed by 13-PROBE-RESULTS.md §A2): the post-step
|
|
748
|
+
* PC landed at JSR+3, reproduced identically across two independent live
|
|
749
|
+
* sessions.
|
|
745
750
|
*/
|
|
746
751
|
export function advanceInstructionsBody({ stepOver = false, count = 1 }: AdvanceInstructionsBodyOptions = {}): Buffer {
|
|
747
752
|
if (!Number.isInteger(count) || count < 1 || count > 0xffff) {
|
|
@@ -816,8 +821,8 @@ export interface ResetBodyOptions {
|
|
|
816
821
|
* [CITED docs/phase0-binmon-findings.md §5]
|
|
817
822
|
*
|
|
818
823
|
* NOT the RESOURCE_SET (0x52) power-cycle hazard CLAUDE.md warns about
|
|
819
|
-
* (`MachineVideoStandard`/`VICIIModel`/`MachinePowerFrequency`,
|
|
820
|
-
*
|
|
824
|
+
* (`MachineVideoStandard`/`VICIIModel`/`MachinePowerFrequency`, the CUT
|
|
825
|
+
* vice_machine_config_get/set pair's resources) -- a distinct opcode, and an agent-requested hard
|
|
821
826
|
* reset via RESET is exactly what DIRECT-06 asks for. It needs no
|
|
822
827
|
* deny-list. This is RESEARCH.md's Pitfall 1; this comment is what stops a
|
|
823
828
|
* later reviewer from "fixing" it by adding one.
|
package/stock-registers.ts
CHANGED
|
@@ -68,12 +68,19 @@ export interface RegisterCatalog {
|
|
|
68
68
|
/** The one module-level catalog map, keyed on the session object itself --
|
|
69
69
|
* NOT on session.client -- so a fresh stockReconnect() (which returns a
|
|
70
70
|
* brand-new session) is indistinguishable from "never fetched" and simply
|
|
71
|
-
* fetches again, with no manual invalidation path required anywhere.
|
|
71
|
+
* fetches again, with no manual invalidation path required anywhere.
|
|
72
|
+
*
|
|
73
|
+
* Caches the in-flight PROMISE, not the resolved catalog (15-04, IN-02):
|
|
74
|
+
* two handlers racing on a fresh session both read `catalogs.get(session)`
|
|
75
|
+
* before either write lands, so caching only the resolved value let both
|
|
76
|
+
* send their own REGISTERS_AVAILABLE. Caching the promise means the second
|
|
77
|
+
* caller awaits the SAME in-flight request. A rejected promise is evicted
|
|
78
|
+
* (see registerCatalogFor()) so a failed fetch is retried, never memoised. */
|
|
72
79
|
// Single-line by design: the ONLY line in this file naming the garbage-
|
|
73
80
|
// collectable, session-keyed map primitive directly (grep-gated -- see
|
|
74
81
|
// this plan's own acceptance criteria). Every other reference goes
|
|
75
82
|
// through this factory, never a second construction call site.
|
|
76
|
-
function freshCatalogMap(): WeakMap<StockConnectSession, RegisterCatalog
|
|
83
|
+
function freshCatalogMap(): WeakMap<StockConnectSession, Promise<RegisterCatalog>> { return new WeakMap<StockConnectSession, Promise<RegisterCatalog>>(); }
|
|
77
84
|
|
|
78
85
|
let catalogs = freshCatalogMap();
|
|
79
86
|
|
|
@@ -86,15 +93,22 @@ export function resetRegisterCatalogsForTest(): void {
|
|
|
86
93
|
|
|
87
94
|
/**
|
|
88
95
|
* Resolves `session`'s register catalog, fetching it through
|
|
89
|
-
* REGISTERS_AVAILABLE (0x83) exactly once and caching the
|
|
90
|
-
* session object
|
|
91
|
-
*
|
|
96
|
+
* REGISTERS_AVAILABLE (0x83) exactly once and caching the IN-FLIGHT
|
|
97
|
+
* PROMISE on the session object -- not just the resolved value, so two
|
|
98
|
+
* concurrent callers on a fresh session (before either fetch has
|
|
99
|
+
* resolved) share the SAME REGISTERS_AVAILABLE round trip rather than
|
|
100
|
+
* each sending their own (15-04, IN-02). Every subsequent call for the
|
|
101
|
+
* SAME session object returns the same promise (and, once it settles,
|
|
102
|
+
* the same resolved catalog) with no further wire traffic.
|
|
92
103
|
*
|
|
93
104
|
* Refuses (throws a plain Error, converted by the caller through
|
|
94
105
|
* convertWireError()) an empty enumeration rather than caching it: a
|
|
95
106
|
* build that enumerates zero registers cannot support
|
|
96
107
|
* vice_registers_set, and that failure must be visible on every call,
|
|
97
|
-
* never silently cached as "zero registers, nothing to resolve".
|
|
108
|
+
* never silently cached as "zero registers, nothing to resolve". A
|
|
109
|
+
* rejected fetch, whether from an empty enumeration or a wire error, is
|
|
110
|
+
* evicted from the cache before this function returns, so the NEXT call retries instead
|
|
111
|
+
* of permanently memoising the failure.
|
|
98
112
|
*/
|
|
99
113
|
export async function registerCatalogFor(session: StockConnectSession): Promise<RegisterCatalog> {
|
|
100
114
|
const existing = catalogs.get(session);
|
|
@@ -102,30 +116,41 @@ export async function registerCatalogFor(session: StockConnectSession): Promise<
|
|
|
102
116
|
return existing;
|
|
103
117
|
}
|
|
104
118
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
const pending = (async (): Promise<RegisterCatalog> => {
|
|
120
|
+
const response = await session.client.send(CommandType.RegistersAvailable, memspaceBody({ memspace: 0x00 }));
|
|
121
|
+
if (response.type !== "registers_available") {
|
|
122
|
+
throw new Error(`registerCatalogFor: expected a registers_available reply, got "${response.type}"`);
|
|
123
|
+
}
|
|
124
|
+
if (response.registers.length === 0) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
"registerCatalogFor: the connected VICE build enumerated zero registers via REGISTERS_AVAILABLE -- " +
|
|
127
|
+
"it cannot support vice_registers_set, and this failure must be named rather than cached as an empty catalog",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
115
130
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
const byName = new Map<string, { id: number; sizeBits: number; name: string }>();
|
|
132
|
+
const byId = new Map<number, { sizeBits: number; name: string }>();
|
|
133
|
+
for (const reg of response.registers) {
|
|
134
|
+
// reg.size is stock-protocol.ts's own field name for the wire's size
|
|
135
|
+
// byte (its parser is unchanged by this plan); this module renames it
|
|
136
|
+
// to sizeBits at the point it enters the catalog so every downstream
|
|
137
|
+
// reader sees the unit named in the type.
|
|
138
|
+
byName.set(reg.name.toUpperCase(), { id: reg.id, sizeBits: reg.size, name: reg.name });
|
|
139
|
+
byId.set(reg.id, { sizeBits: reg.size, name: reg.name });
|
|
140
|
+
}
|
|
141
|
+
return { byName, byId };
|
|
142
|
+
})();
|
|
143
|
+
|
|
144
|
+
catalogs.set(session, pending);
|
|
145
|
+
// Evict on rejection so a failed fetch is retried by the next call
|
|
146
|
+
// rather than memoised forever -- only if no newer promise has already
|
|
147
|
+
// replaced this one for the same session.
|
|
148
|
+
pending.catch(() => {
|
|
149
|
+
if (catalogs.get(session) === pending) {
|
|
150
|
+
catalogs.delete(session);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
return pending;
|
|
129
154
|
}
|
|
130
155
|
|
|
131
156
|
// ---------------------------------------------------------------------------
|
package/stock-sprites.ts
CHANGED
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
// PROVENANCE (required reading before touching the four geometry
|
|
13
13
|
// functions below): vicBank(), vicBankBase(), screenBase() and
|
|
14
14
|
// spriteDataAddress() are PORTED, NOT RE-DERIVED, from
|
|
15
|
-
//
|
|
15
|
+
// src/skills/c64-ram-capture/scripts/dump-artifacts.mjs's own
|
|
16
16
|
// vicBank()/screenBase()/spriteDataAddresses map, which carries a
|
|
17
17
|
// committed, verified fixture: dd00_raw=193 (0xC1), d018_raw=49 (0x31) ->
|
|
18
18
|
// screen_base=35840. stock-sprites.test.ts re-asserts the SAME fixture as
|
|
19
19
|
// its own cross-check -- do not change any of the four expressions without
|
|
20
20
|
// also updating that committed fixture's provenance. The skill's
|
|
21
|
-
// JavaScript is copied here, never imported at runtime --
|
|
22
|
-
// is a different package, absent from
|
|
21
|
+
// JavaScript is copied here, never imported at runtime -- src/skills/
|
|
22
|
+
// is a different package, absent from src/mcp/vice's files[], so a
|
|
23
23
|
// runtime cross-package import would be missing from the published
|
|
24
24
|
// tarball.
|
|
25
25
|
//
|
package/stock-symbols.ts
CHANGED
|
@@ -26,15 +26,15 @@
|
|
|
26
26
|
// The confirmed input format is a VICE label file, one `al C:xxxx .Name`
|
|
27
27
|
// line per symbol, verified against ACME's `--vicelabels` output via
|
|
28
28
|
// acme-build/scripts/acme.mjs's own parser (curateLabels(),
|
|
29
|
-
// `/^al\s+C:[0-9a-f]+\s+\.(\S+)/i`). VERIFIED (Phase 9,
|
|
30
|
-
//
|
|
29
|
+
// `/^al\s+C:[0-9a-f]+\s+\.(\S+)/i`). VERIFIED (Phase 9, ANNO-16(c)):
|
|
30
|
+
// the external analyser 0.9.20's `--export_lbl` was run against the
|
|
31
31
|
// probe-illegal.prg-derived fixture and emitted `al C:0810 .init_screen`,
|
|
32
32
|
// which matches this module's own VICE_LABEL_LINE_RE
|
|
33
33
|
// (`/^al\s+C:([0-9a-fA-F]{1,4})\s+\.(\S+)/`) exactly. This claim is SCOPED to
|
|
34
|
-
//
|
|
34
|
+
// the external analyser 0.9.20 and that fixture -- not to all inputs forever (the
|
|
35
35
|
// same scoping caveat ROADMAP.md applies to Phase 9's criterion 3(3) `pass`).
|
|
36
36
|
// The parser below still SKIPS unrecognised lines rather than refusing the
|
|
37
|
-
// whole file: a future
|
|
37
|
+
// whole file: a future analyser version, a hand-edited label file, or
|
|
38
38
|
// a different exporter entirely can still produce lines this format should
|
|
39
39
|
// tolerate rather than reject outright.
|
|
40
40
|
//
|
|
@@ -82,8 +82,8 @@ const VICE_LABEL_LINE_RE = /^al\s+C:([0-9a-fA-F]{1,4})\s+\.(\S+)/;
|
|
|
82
82
|
|
|
83
83
|
/** T-05-02-03: three independent resource ceilings, each refusing with both
|
|
84
84
|
* the observed value and the limit named. `MAX_LABEL_FILE_BYTES` is exported
|
|
85
|
-
* (11-08, Rule A20) so `
|
|
86
|
-
* can apply the SAME byte ceiling to
|
|
85
|
+
* (11-08, Rule A20) so `anno-symbols.ts`'s `exportLabels()`/`importLabels()`
|
|
86
|
+
* can apply the SAME byte ceiling to an external-analyser-produced/-consumed
|
|
87
87
|
* `.lbl` file before ever calling `parseViceLabelFile()` below -- never a
|
|
88
88
|
* second hand-copied number. */
|
|
89
89
|
export const MAX_LABEL_FILE_BYTES = 2 * 1024 * 1024;
|
|
@@ -201,9 +201,9 @@ function resolveLabelFilePath(pathArg: unknown): string {
|
|
|
201
201
|
// ---------------------------------------------------------------------------
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
-
* Exported (11-08, Rule A20) so `
|
|
205
|
-
*
|
|
206
|
-
* BEFORE it is ever handed to a spawned
|
|
204
|
+
* Exported (11-08, Rule A20) so `anno-symbols.ts` can validate a
|
|
205
|
+
* external-analyser-produced `.lbl` file (or check a caller-supplied one
|
|
206
|
+
* BEFORE it is ever handed to a spawned analyser child) through THIS
|
|
207
207
|
* parser -- the ONE `al C:xxxx .Name` reader in this repo -- rather than
|
|
208
208
|
* adding a second copy of `VICE_LABEL_LINE_RE`. Ceiling violations
|
|
209
209
|
* (`MAX_LABEL_FILE_LINES`/`MAX_SYMBOLS`) throw `StockSymbolsError` exactly as
|
package/stock-timing.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// - Never assign `cycles: 0` or `cycles: null` for an unmeasurable
|
|
22
22
|
// bracket. `measurable: false` with a `reason` and NO `cycles` key at
|
|
23
23
|
// all is the only honest shape -- see the incident this rule exists to
|
|
24
|
-
// prevent:
|
|
24
|
+
// prevent: `src/skills/c64-program-recon/references/observation-hazards.md`'s
|
|
25
25
|
// record of the fork's stopwatch reading 258,504,308 cycles and being
|
|
26
26
|
// trusted as fact.
|
|
27
27
|
// - Never hardcode a register id for LIN/CYC/PC. `registerCatalogFor()`
|
package/stock-vicii.ts
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
//
|
|
62
62
|
// FIELD-NAME PROVENANCE (Assumption A4's mitigation): every bit-field name
|
|
63
63
|
// below was transcribed once from
|
|
64
|
-
//
|
|
64
|
+
// src/skills/c64-memory-mapping/memmap.json's own entries for $D011
|
|
65
65
|
// (Screen control register #1 / VIC Control Register), $D012 (raster line),
|
|
66
66
|
// $D016 (Screen control register #2 / VIC Control Register), $D018 (Memory
|
|
67
67
|
// setup register / VIC Memory Control Register), $D019 (Interrupt status
|
package/version.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The ONE authoritative implementation of this project's version-resolution
|
|
2
2
|
// algorithm (D-5). Before this file existed the repo carried FOUR
|
|
3
3
|
// hand-maintained version strings and none of them were true:
|
|
4
|
-
//
|
|
4
|
+
// `src/mcp/vice/package.json` and `installer/package.json` were stale
|
|
5
5
|
// placeholders CI never touched between releases, `.claude-plugin/plugin.json`
|
|
6
6
|
// was bumped by NO automation at all, and `vice-proxy.ts`'s own
|
|
7
7
|
// `PROXY_VERSION` literal -- advertised to every MCP client over
|