Files
Drew T bfea4affd8 feat(draw): route the model tier off the prior RESIDUAL CLASS, not nins
Measured on S71's own wave: wall-clock tracks iteration count, and iteration count
tracks the residual class, not size. A 26-instruction function took 18 min / 31 tool
calls (regalloc, finished NEAR); a 122-instruction one took 80 s / 10. The 20-33 min
runs were all compiler-internal residuals — scheduling ties, birthing boost, register
colouring, LUID order — where every hypothesis costs a compile-and-measure cycle.

arm_for keys on nins alone, so a 47-instruction regalloc wall could not be drawn at
the higher tier and nothing escalates mid-run. arm_from_history() now reads the
function's own journal notes at draw time and returns fable when they name one of
those classes; it never downgrades the size ladder's choice.
R39 control over 3,147 functions with history x 3 bands = 9,441 decisions:
4,020 upgrades (43%), 0 downgrades.

The control's FIRST form passed over an empty set — it keyed on journal rows carrying
a binary, and there are none: the agent verdict schema never had that field, so every
historical note is name-keyed and the same name is a different function in another
overlay (§238). claude_wave_draft.js's VERDICT now requires `binary`, so new rows are
exact. Cookbook §413.
2026-09-02 01:29:59 -06:00

60 lines
4.4 KiB
JavaScript

export const meta = {
name: 'claude-wave-draft',
description: 'Claude-subagent drafting wave (P31 S62 T5; born as the T4 model-ladder probe): one agent per target, model = target.arm, identical api_agent packs; drafts to <wave>/<arm>/<fn>.c',
phases: [
{ title: 'Draft', detail: 'one drafting agent per target (sonnet <=120 ins / opus >120 + residue), local match_one oracle only; tools/wave_judge.py is the judge' },
],
}
// args: { wave: '.run/t5a', targets: [{name, binary, nins, sub, arm}, ...] } — from tools/t5_targets.py
// (targets.json + SYS.md + packs/ live under <wave>, built by tools/claude_wave_packs.py)
const REPO = '/home/musashi/bfm-decomp'
const WAVE = args.wave
const TARGETS = args.targets
const VERDICT = {
type: 'object',
properties: {
fn: { type: 'string' },
// BINARY IS PART OF A FUNCTION'S IDENTITY (R48/§238). Without it every journal note is
// name-keyed, and the same name is a DIFFERENT function in another overlay — so the S71
// past-attempt fuel (§411) could serve one overlay's history to another's target. Every
// historical journal row lacks this; stamping it now makes future ones exact.
binary: { type: 'string' },
arm: { type: 'string' },
status: { type: 'string', enum: ['MATCH', 'NEAR', 'FAIL', 'NO-DRAFT'] },
closeness: { type: ['integer', 'null'] },
compiles: { type: 'boolean' },
draft_path: { type: 'string' },
note: { type: 'string' },
},
required: ['fn', 'binary', 'arm', 'status', 'closeness', 'compiles', 'draft_path', 'note'],
}
phase('Draft')
log(`wave ${WAVE}: ${TARGETS.length} targets (${TARGETS.filter(t => t.arm === 'sonnet').length} sonnet / ${TARGETS.filter(t => t.arm === 'opus').length} opus)`)
const results = await parallel(TARGETS.map(t => () => agent(
`You are one drafting agent of a Claude wave in the Brave Fencer Musashi decompilation repo at ${REPO}.
Byte-match ONE PlayStation function: ${t.name} (${t.nins} instructions, binary ${t.binary}).
FIRST read these two files in full — they are your complete brief and your ground truth pointers:
${REPO}/${WAVE}/SYS.md (the laws; it names tools read_file/grep/match_one/submit — use the CLI equivalents below)
${REPO}/${WAVE}/packs/${t.name}.md (the target pack: asm path, destination TU, card fuel, a warm-start body, any gate feedback)
CLI equivalents (run from ${REPO}):
read_file -> cat / sed -n
grep -> rg or grep -n (start at docs/cookbook-index.md; \`.venv/bin/python tools/cookbook_index.py --resolve N\` resolves a line number)
match_one -> .venv/bin/python tools/match_one.py ${t.name} --c <your_draft.c> --asm-subdir ${t.sub} --json
(prints JSON with status MATCH/near/... and closeness = masked mismatched instruction count; it is free — use it often)
submit -> write your FINAL draft to ${REPO}/${WAVE}/${t.arm}/${t.name}.c (mkdir -p the dir) and return the JSON verdict.
HARD RULES: never modify anything under src/, config/, include/, asm/, build/ or run make; never touch other agents' files; write only to ${WAVE}/${t.arm}/${t.name}.c and scratch under ${WAVE}/${t.arm}/scratch_${t.name}/. Budget: up to ~24 compile/match_one iterations, then stop honestly.
Your final answer is the JSON verdict only: fn, binary="${t.binary}", arm="${t.arm}", status (MATCH if match_one says MATCH; NEAR if it compiles with closeness>0; FAIL if it never compiled; NO-DRAFT if you wrote nothing), closeness (integer or null), compiles, draft_path, note (one line: what blocked you, or which cookbook § unlocked it).`,
{ label: `${t.arm}:${t.name}`, phase: 'Draft', model: t.arm, schema: VERDICT }
).then(v => v || { fn: t.name, binary: t.binary, arm: t.arm, status: 'NO-DRAFT', closeness: null, compiles: false, draft_path: '', note: 'agent returned null' })
.catch(e => ({ fn: t.name, binary: t.binary, arm: t.arm, status: 'NO-DRAFT', closeness: null, compiles: false, draft_path: '', note: 'agent error: ' + String(e).slice(0, 120) }))))
const byArm = {}
for (const r of results.filter(Boolean)) { (byArm[r.arm] = byArm[r.arm] || []).push(r) }
for (const arm of Object.keys(byArm)) {
const rs = byArm[arm]
log(`${arm}: self-reported MATCH ${rs.filter(r => r.status === 'MATCH').length} / NEAR ${rs.filter(r => r.status === 'NEAR').length} / FAIL ${rs.filter(r => r.status === 'FAIL').length} / NO-DRAFT ${rs.filter(r => r.status === 'NO-DRAFT').length} of ${rs.length} (the gate decides — R14)`)
}
return { wave: WAVE, results }