mirror of
https://github.com/pret/pokeyellow.git
synced 2026-09-15 05:55:17 -05:00
Refactor agent swarm config and expand work_queue status machine
- Expand `work_queue` status pipeline: `needs_translation` -> `in_progress` -> `translated` -> `wired` -> `verified`. - Add `reset`, `wire`, and `verify` commands to `work_queue`. - Add safety guard to `work_queue place`: prevents overwriting output file if scratch path equals output path. - Update `build_index` to only mark labels as verified/translated if `label_exists_in_file` succeeds (greps `^LabelName:`). - Fix engine/ prefix paths in `build_index` `TRANSLATED_MAP`. - Auto-migrate DB on connection: `complete` -> `translated`, `unverified` -> `needs_translation`. - Restructure agent files into scoped roles (`.agents/roles/`) and skills (`.agents/skills/`). - Trim `.agents/AGENTS.md` to ~100 lines containing only overview, rules, quick-ref, and role table. - Consolidate agent sandbox configurations into `.agents/settings.json` and empty `.agents/hooks.json`.
This commit is contained in:
@@ -1,323 +1,89 @@
|
||||
# Antigravity Pokemon Yellow 32-bit PMODE Port — Agent Swarm
|
||||
# Antigravity Pokemon Yellow DOS Port — Agent Swarm
|
||||
|
||||
## Overview
|
||||
|
||||
Claude is **not** part of this swarm. Claude is used directly via Claude Code 1:1
|
||||
for complex work, architecture decisions, and callsite wiring. The swarm handles
|
||||
bulk translation of simple-category functions and placing them into the correct
|
||||
files — nothing more.
|
||||
Claude Code handles architecture, complex functions, and live-graph wiring.
|
||||
The swarm handles bulk translation of `simple`-category functions only.
|
||||
|
||||
```
|
||||
Dispatch_Manager (gemini-3.1-pro, effort:high) ← top-level coordinator
|
||||
├── Code_Worker_1..5 (gemini-3.5-flash, effort:high)
|
||||
├── Integration_Agent (gemini-3.5-flash, effort:high)
|
||||
└── Docs_Commit_Agent (gemini-3.1-pro, effort:high)
|
||||
Dispatch_Manager (gemini-3.1-pro) ← coordinator
|
||||
├── Code_Worker_1..5 (gemini-3.5-flash)
|
||||
├── Integration_Agent (gemini-3.5-flash)
|
||||
└── Docs_Commit_Agent (gemini-3.1-pro)
|
||||
```
|
||||
|
||||
**Hard boundary**: The swarm places translated code in the right files. It does
|
||||
**not** wire functions into the game loop. Callsite connections (adding `call`
|
||||
instructions into existing engine code, modifying `OverworldLoop`, etc.) are
|
||||
reserved for Claude Code sessions where the user has direct control.
|
||||
**Hard boundary**: Swarm places translated code. It does **not** wire functions
|
||||
into the live game loop. Live-graph connections (`OverworldLoop`, `EnterMap`,
|
||||
`DelayFrame` callees) are Claude Code only.
|
||||
|
||||
---
|
||||
|
||||
## Required Reading (all agents that write or review NASM)
|
||||
## Role Files (read your role file before doing anything)
|
||||
|
||||
Every agent that generates, reviews, or integrates NASM code **must** read these
|
||||
before doing any work. They are not optional background — violations will cause
|
||||
silent runtime bugs or corrupt glitch behaviour.
|
||||
|
||||
| Document | Why it matters |
|
||||
| Role | File |
|
||||
|---|---|
|
||||
| `CLAUDE.md` | Register map, memory model, DPMI gotchas, build conventions |
|
||||
| `docs/register_map.md` | Canonical SM83→x86 register assignments |
|
||||
| `docs/386_optimization_strategy.md` | Instruction selection rules for 386 targets |
|
||||
| `docs/bugs_and_glitches.md` | Which SM83 bugs to preserve and at what fix level |
|
||||
| `docs/glitch_safety.md` | Which glitches are safe to emulate vs. dangerous under DPMI |
|
||||
|
||||
Dispatch_Manager must read all five before writing any ticket.
|
||||
Code Workers must read all five before translating any function.
|
||||
Integration_Agent must read `CLAUDE.md` and `docs/register_map.md` before placing files.
|
||||
Docs_Commit_Agent must read `CLAUDE.md` for commit conventions.
|
||||
| Dispatch_Manager | `.agents/roles/dispatch.md` |
|
||||
| Code_Worker | `.agents/roles/worker.md` |
|
||||
| Integration_Agent | `.agents/roles/integration.md` |
|
||||
| Docs_Commit_Agent | `.agents/roles/docs.md` |
|
||||
|
||||
---
|
||||
|
||||
## Role: Dispatch_Manager
|
||||
- **Model**: `gemini-3.1-pro`
|
||||
- **Settings**: `effort: high`
|
||||
- **Objective**: Top-level swarm coordinator. Reads the work queue, writes
|
||||
per-function tickets for Code Workers, reviews returned translations, marks
|
||||
jobs complete, and hands finished files to the Integration and Docs agents.
|
||||
Does **not** touch `complex`-category functions — those are left for Claude.
|
||||
## Skills (load on demand with `agy skill <name>`)
|
||||
|
||||
### Work Queue Interface
|
||||
Queue lives in `dos_port/tools/translation.db`. Use `dos_port/tools/work_queue`
|
||||
(executable, no extension, outputs JSON):
|
||||
| Skill | When to load |
|
||||
|---|---|
|
||||
| `register-map` | Before writing any NASM |
|
||||
| `path-map` | Before placing any file |
|
||||
| `386-checklist` | When choosing instructions |
|
||||
| `bug-check` | When BUG/GLITCH annotation needed |
|
||||
| `glitch-escalation` | When hitting a $FF__ register |
|
||||
| `commit-format` | Before committing |
|
||||
|
||||
---
|
||||
|
||||
## Work Queue Quick Reference
|
||||
|
||||
```sh
|
||||
dos_port/tools/work_queue status
|
||||
dos_port/tools/work_queue claim --agent Dispatch_Manager --count 5 --category simple
|
||||
dos_port/tools/work_queue fail --id <ID> --notes "reason"
|
||||
dos_port/tools/work_queue list --category simple --status needs_translation --limit 20
|
||||
dos_port/tools/work_queue pending-placement # jobs ready for Integration Agent
|
||||
dos_port/tools/work_queue claim --agent <NAME> --count 5 --category simple
|
||||
dos_port/tools/work_queue pending-placement
|
||||
dos_port/tools/work_queue list --status needs_translation --category simple --limit 20
|
||||
```
|
||||
|
||||
See `dos_port/tools/work_queue --help` for the full reference.
|
||||
Run `dos_port/tools/work_queue --help` for full command reference.
|
||||
|
||||
### Scratch pad
|
||||
Workers write their output to `dos_port/scratch/` — a fully untracked ephemeral
|
||||
directory (gitignored). Each file is named `<id>__<label>.asm` and must begin
|
||||
with a manifest header (see Code Worker section below). The Dispatch Manager
|
||||
verifies the scratch file assembles, then calls:
|
||||
Status pipeline: `needs_translation → in_progress → translated → [wired → verified]`
|
||||
|
||||
```sh
|
||||
dos_port/tools/work_queue complete --id <ID> --scratch dos_port/scratch/<id>__<label>.asm --agent Dispatch_Manager
|
||||
```
|
||||
|
||||
The Integration Agent picks up everything in `pending-placement`, moves each
|
||||
file into `dos_port/src/`, and calls `work_queue place` to record the final path.
|
||||
|
||||
### Ticket format (sent to each Code Worker)
|
||||
Each ticket must include:
|
||||
1. Pret source file path and the exact label to translate
|
||||
2. Target output file path under `dos_port/src/`
|
||||
3. Relevant rows from `docs/register_map.md` (copy them verbatim)
|
||||
4. `gb_memmap.inc` constants used by this function, with hex values pre-resolved
|
||||
5. Any `; BUG()` / `; GLITCH:` annotations from `docs/bugs_and_glitches.md`
|
||||
6. The exact `nasm -f coff -o /dev/null <file>` command to verify assembly
|
||||
7. The correct include lines to paste at the top of the file (see below)
|
||||
|
||||
Vague tickets are not acceptable. Workers are not smart enough to look things up.
|
||||
|
||||
### Include path rule (copy into every ticket verbatim)
|
||||
NASM is invoked from the `dos_port/` directory with `-I include/ -I .`.
|
||||
Include paths must use the **short bare name only** — no directory prefix:
|
||||
|
||||
```nasm
|
||||
%include "gb_memmap.inc" ; correct
|
||||
%include "gb_macros.inc" ; correct
|
||||
|
||||
%include "dos_port/include/gb_memmap.inc" ; WRONG — breaks the build
|
||||
%include "include/gb_memmap.inc" ; WRONG — breaks the build
|
||||
```
|
||||
|
||||
Paste this rule into every worker ticket so it is impossible to miss.
|
||||
|
||||
### Dispatch rules
|
||||
- Never assign two workers to the same output file simultaneously.
|
||||
- Only dispatch `simple`-category jobs. If a job turns out to require hardware
|
||||
I/O, graphics, or audio, call `work_queue fail` and leave it for Claude.
|
||||
- Maximum 5 Code Workers active at once.
|
||||
- After a worker returns, verify the file assembles before calling `work_queue complete`.
|
||||
`wired` and `verified` are Claude Code session transitions only.
|
||||
|
||||
---
|
||||
|
||||
## Role: Integration_Agent
|
||||
- **Model**: `gemini-3.5-flash`
|
||||
- **Settings**: `effort: high`
|
||||
- **Objective**: Place translated functions into the correct files so the build
|
||||
can see them. This means adding `%include` lines to the appropriate aggregator
|
||||
(e.g. `dos_port/src/home.asm`, `dos_port/src/engine/battle/experience.asm`)
|
||||
and adding object-file rules to the `dos_port/Makefile` where needed. That is
|
||||
the full scope of this role.
|
||||
## Category Definitions
|
||||
|
||||
### What this agent MUST NOT do
|
||||
- **No live-graph wiring.** Do not edit any function that is already reachable
|
||||
from the running game loop (`OverworldLoop`, `EnterMap`, `DelayFrame`, and
|
||||
everything they transitively call). Adding a `call NewFunction` inside one of
|
||||
those would cause untested code to execute during normal gameplay.
|
||||
That boundary is enforced in Claude Code sessions only.
|
||||
- Translated functions may freely call other translated functions — that is
|
||||
expected and correct. The restriction is one-directional: live code must not
|
||||
gain new calls into unwired code.
|
||||
- Do not modify any already-wired file beyond adding a `%include` to an
|
||||
aggregator or a Makefile rule.
|
||||
- Do not touch `unverified`-status files. Only `complete`-status translations
|
||||
may be placed.
|
||||
- Do not delete scratch files; the Dispatch Manager owns them. They disappear
|
||||
naturally since the whole `dos_port/scratch/` directory is gitignored.
|
||||
- DO NOT WRITE PYTHON SCRIPTS TO EDIT FILES!!! YOU HAVE TOOLS FOR THIS!!!
|
||||
**`simple`** → swarm handles: pure arithmetic, data lookup, flag set/clear,
|
||||
inventory math, battle formulas, BCD, random numbers. No `$FF__` I/O.
|
||||
|
||||
### Integration checklist (per function)
|
||||
1. Run `dos_port/tools/work_queue pending-placement` to see what's ready.
|
||||
2. Read the manifest header at the top of the scratch file
|
||||
(`dos_port/scratch/<id>__<label>.asm`) to determine the source origin.
|
||||
3. Decide the correct `dos_port/src/` destination (mirror the pret source tree
|
||||
under `dos_port/src/`). Fill in the `target` and `aggregator` fields in the
|
||||
header if helpful for the audit trail.
|
||||
4. Copy the file to its `dos_port/src/` destination. The scratch copy remains
|
||||
until the swarm session ends.
|
||||
5. Add a `%include` line in the appropriate aggregator file.
|
||||
6. If the file is a new compilation unit, add a `$(OBJ)/name.o` rule to the
|
||||
Makefile and append the object to `OBJS`.
|
||||
7. Run `nasm -f coff -o /dev/null <aggregator>` — must pass.
|
||||
8. Run `make -C dos_port` — must pass clean with no new warnings.
|
||||
9. Call `dos_port/tools/work_queue place --id <ID> --output dos_port/src/<path>`.
|
||||
10. Hand the diff to `Docs_Commit_Agent`.
|
||||
**`complex`** → Claude Code only: anything touching PPU, VGA, OAM, tile cache,
|
||||
audio, joypad, menus with tile rendering, map transitions, pikachu, link cable.
|
||||
|
||||
---
|
||||
|
||||
## Role: Docs_Commit_Agent
|
||||
- **Model**: `gemini-3.5-flash`
|
||||
- **Settings**: `effort: high`
|
||||
- **Objective**: Maintain documentation and execute git commits.
|
||||
- Append a structured entry to `docs/translation_log.md` for every newly-placed
|
||||
translation, using the pre-formatted entry from the queue tool (see below).
|
||||
- Write a concise git commit message (see existing commit history for style).
|
||||
- Stage exactly the files changed — never `git add -A`.
|
||||
- Execute `git commit`. Does not push.
|
||||
## Swarm Rules
|
||||
|
||||
### Writing translation log entries
|
||||
For each function Integration Agent just placed, run:
|
||||
|
||||
```sh
|
||||
dos_port/tools/work_queue translation-log-entry --id <ID>
|
||||
```
|
||||
|
||||
This returns JSON with an `entry` field: a ready-to-append Markdown block
|
||||
populated from the worker's notes (registers used, H-flag involvement, bug tags,
|
||||
free-text notes). Append the `entry` string verbatim to `docs/translation_log.md`.
|
||||
If a worker left notes blank, fill in what you can infer from the diff before
|
||||
committing.
|
||||
|
||||
### Commit message conventions
|
||||
- Subject ≤ 72 chars.
|
||||
- Body: SM83 → x86 translation notes (register decisions, bug-fix level used).
|
||||
- Trailer: `Co-Authored-By: Gemini <noreply@google.com>` for swarm-generated content.
|
||||
- Never commit `.o`, `.orig`, `DUMP.BIN`, `FRAME.BIN`, or `translation.db`.
|
||||
|
||||
---
|
||||
|
||||
## Role: Code_Worker (×5 instances)
|
||||
- **Model**: `gemini-3.5-flash`
|
||||
- **Settings**: `effort: high`
|
||||
- **Objective**: Translate one SM83 function to x86 NASM 32-bit protected mode
|
||||
per the ticket from Dispatch_Manager. One ticket per worker at a time.
|
||||
|
||||
### Scratch file format
|
||||
Every worker output must go to `dos_port/scratch/<id>__<label>.asm`.
|
||||
Get the pre-formatted manifest header by running:
|
||||
|
||||
```sh
|
||||
dos_port/tools/work_queue manifest --id <ID>
|
||||
```
|
||||
|
||||
Write that header verbatim at the top of the file, **fill in the four WORKER
|
||||
NOTES fields**, then append the translated NASM code beneath the `CODE BELOW`
|
||||
line. The header looks like:
|
||||
|
||||
```nasm
|
||||
; ╔══════════════════════════════════════════════════════════╗
|
||||
; ║ PKMNDOS TRANSLATION MANIFEST ║
|
||||
; ╚══════════════════════════════════════════════════════════╝
|
||||
; queue_id : 1234
|
||||
; label : CalcExperience
|
||||
; source : engine/battle/experience.asm
|
||||
; category : simple
|
||||
; scratch : dos_port/scratch/1234__CalcExperience.asm
|
||||
; -----------------------------------------------------------
|
||||
; target : (Integration Agent fills this in)
|
||||
; aggregator : (Integration Agent fills this in)
|
||||
; -----------------------------------------------------------
|
||||
; WORKER NOTES — fill in before calling work_queue complete
|
||||
; registers : HL→ESI for exp table ptr, A→AL, BC→BX for growth rate
|
||||
; hflag : not involved
|
||||
; bug_tags : none
|
||||
; notes : used imul for exp formula; SM83 used 16-bit mul via DE pair
|
||||
; ╔══════════════════════════════════════════════════════════╗
|
||||
; ║ CODE BELOW — do not modify the header above ║
|
||||
; ╚══════════════════════════════════════════════════════════╝
|
||||
|
||||
CalcExperience:
|
||||
...
|
||||
```
|
||||
|
||||
`work_queue complete` automatically parses the four notes fields from the header
|
||||
and stores them in the DB. Leave a field as its placeholder text (in parentheses)
|
||||
if it genuinely does not apply — the parser ignores unfilled placeholders.
|
||||
|
||||
### Mandatory checklist
|
||||
1. Read the exact pret source label from the ticket. Read surrounding context.
|
||||
2. Check `docs/bugs_and_glitches.md` for any entry matching this function.
|
||||
If found, apply the appropriate `; BUG(level):` block at the affected site.
|
||||
3. Check `docs/glitch_safety.md` — if the function is involved in a known
|
||||
glitch, verify the glitch is safe to emulate under DPMI before translating.
|
||||
Emit `; GLITCH: <name> — Safety: <verdict>` at the relevant site.
|
||||
4. Run `dos_port/tools/work_queue manifest --id <ID>` and write the header to
|
||||
`dos_port/scratch/<id>__<label>.asm`.
|
||||
5. Translate beneath the header using the register mapping from the ticket.
|
||||
Use `[EBP + constant]` for all GB memory. Emit `; TODO-HW:` for I/O hits.
|
||||
6. Fill in the four WORKER NOTES fields in the header before finishing.
|
||||
7. Run `nasm -f coff -o /dev/null dos_port/scratch/<id>__<label>.asm` — must
|
||||
assemble clean.
|
||||
8. Return the scratch path and nasm stdout to Dispatch_Manager.
|
||||
|
||||
### Hard limits
|
||||
- No spawning sub-agents.
|
||||
- No touching graphics, VGA, OAM, VRAM, audio, or joypad code.
|
||||
- Write only to `dos_port/scratch/` — do not modify any existing file.
|
||||
- Do not add `%include` lines or Makefile rules — that is Integration Agent's job.
|
||||
- `call` instructions inside the translated function are expected and correct.
|
||||
Do not add `call` to any *existing* file outside the scratch output.
|
||||
- `%include` lines must use bare filenames only: `%include "gb_memmap.inc"`.
|
||||
Never write `%include "dos_port/include/gb_memmap.inc"` or any path prefix —
|
||||
NASM is invoked from `dos_port/` with `-I include/` so the prefix breaks assembly.
|
||||
- If the function touches a hardware register (`$FF__`) not in the ticket, stop
|
||||
and report back immediately. Do not guess.
|
||||
|
||||
---
|
||||
|
||||
## Work Queue — Category Definitions
|
||||
|
||||
### `simple` → swarm handles
|
||||
Pure arithmetic, data manipulation, flag operations. No `$FF__` register
|
||||
accesses, no tile rendering, no sprite blitting, no sound.
|
||||
|
||||
Examples: battle damage/EXP formulas, PP decrement, random number generation,
|
||||
BCD arithmetic, inventory management, status flag set/clear, item data lookups,
|
||||
trainer-sight geometry, wild encounter rate math.
|
||||
|
||||
### `complex` → Claude Code 1:1 only
|
||||
Anything touching hardware-mapped registers, the software PPU, the VGA blitter,
|
||||
OAM shadow buffer, tile cache, audio engine, or central game-loop dispatch.
|
||||
Also: menus with tile rendering, map transitions, sprite animation, pikachu
|
||||
follow/PCM, link cable, printer, and battle cutscenes.
|
||||
|
||||
Examples: LCD control, vblank, palette loading, OAM DMA, tilemap updates, text
|
||||
rendering, window layer, overworld map loading, battle animations, movie sequences.
|
||||
|
||||
---
|
||||
|
||||
## Swarm Rules & Safeguards
|
||||
|
||||
1. **No Claude in the loop.** The swarm does not spawn Claude agents. Claude
|
||||
operates independently via Claude Code.
|
||||
2. **Live-graph boundary is inviolable.** No agent may edit a function that is
|
||||
already reachable from the running game loop to make it call newly-placed
|
||||
code. Translated functions calling each other is fine — the restriction is
|
||||
that live, tested code must not gain new edges into untested translations.
|
||||
That wiring happens in Claude Code sessions.
|
||||
3. **No direct DB access.** Never run `sqlite3` or any raw SQL against
|
||||
`translation.db`. The table is named `functions`, not `queue` — direct SQL
|
||||
will silently target the wrong table or bypass the audit log entirely.
|
||||
Every queue mutation must go through `dos_port/tools/work_queue`. The correct
|
||||
command for escalating a misclassified job is:
|
||||
`work_queue recategorize --id <ID> --category complex --notes "reason"`
|
||||
4. **No parallel file edits.** Dispatch must not assign two workers to the same
|
||||
output file. Parallelism is at the file level only.
|
||||
5. **Work queue is the source of truth.** Never mark complete outside
|
||||
`work_queue complete`. Never place a function that is not `status=complete`.
|
||||
6. **Unverified = blocked.** Files with `status=unverified` require a Claude
|
||||
review before they can be placed. The swarm does not touch them.
|
||||
7. **No `git add -A`.** Stage only files changed by the current work unit.
|
||||
8. **No `--no-verify`.** Never skip pre-commit hooks.
|
||||
9. **Required reading is mandatory.** See the Required Reading table near the top
|
||||
of this file. All five documents must be read before any NASM is written.
|
||||
Ignorance of a bug or glitch safety ruling is not an acceptable excuse.
|
||||
10. **Hardware escalation.** If a `simple` ticket hits a `$FF__` register or
|
||||
calls a graphics/audio routine, call `work_queue recategorize --category complex`
|
||||
then `work_queue fail`, and leave it for Claude.
|
||||
11. **Strict Command Invocation.** Whenever you execute a custom script, you MUST use the exact bare path (e.g. `dos_port/tools/work_queue`). NEVER prefix it with `./` or `/bin/bash` or `sh`. Do not vary this permutation under any circumstances.
|
||||
1. **No Claude in the loop.** Swarm does not spawn Claude agents.
|
||||
2. **Live-graph boundary is inviolable.** No new edges into untested code from
|
||||
live-game-loop functions. Translated functions calling each other is fine.
|
||||
3. **No direct DB access.** Never run `sqlite3` on `translation.db`.
|
||||
Use `dos_port/tools/work_queue` exclusively.
|
||||
4. **No parallel file edits.** One worker per output file at a time.
|
||||
5. **Work queue is source of truth.** Never mark translated outside the tool.
|
||||
6. **No `git add -A`.** Stage only files changed by the current work unit.
|
||||
7. **No `--no-verify`.** Never skip pre-commit hooks.
|
||||
8. **Hardware escalation.** `$FF__` hit on a simple job → `recategorize complex` + `fail`.
|
||||
9. **Strict command invocation.** Use exact bare path `dos_port/tools/work_queue`.
|
||||
Never prefix with `./`, `/bin/bash`, or `sh`.
|
||||
10. **`complete` requires status=translated.** `place` requires `output ≠ scratch`.
|
||||
|
||||
---
|
||||
|
||||
@@ -328,14 +94,9 @@ dos_port/tools/build_index # non-destructive, adds only new rows
|
||||
dos_port/tools/build_index --rebuild # full reset (clears manual status changes)
|
||||
```
|
||||
|
||||
Quick queue checks:
|
||||
```sh
|
||||
dos_port/tools/work_queue list --category simple --status needs_translation --limit 20
|
||||
dos_port/tools/work_queue list --category complex --status needs_translation --limit 20
|
||||
dos_port/tools/work_queue status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agent Invocation Workaround
|
||||
**IMPORTANT**: Due to issues with dynamic subagent registration, do not use `define_subagent` or attempt to invoke agents by their proper names (e.g. `Integration_Agent`). Instead, when you invoke any of the swarm agents (`Code_Worker`, `Integration_Agent`, `Docs_Commit_Agent`), you MUST use `TypeName: self` in your `invoke_subagent` call and supply the entire system prompt and ticket instructions directly in the `Prompt` field. This ensures the dispatch actually works.
|
||||
## Agent Invocation Note
|
||||
|
||||
Due to dynamic subagent registration, use `TypeName: self` in `invoke_subagent`
|
||||
and supply the full system prompt + ticket in the `Prompt` field.
|
||||
|
||||
@@ -1,23 +1 @@
|
||||
{
|
||||
"antigravity.security.reviewPolicy": "proceed-in-sandbox",
|
||||
"enableTerminalSandbox": true,
|
||||
"antigravity.security.strictMode": false,
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"command(*)",
|
||||
"function(*)"
|
||||
],
|
||||
"ask": [],
|
||||
"deny": [
|
||||
"command(dd *)",
|
||||
"command(dd)",
|
||||
"command(git push *)",
|
||||
"command(git push)"
|
||||
]
|
||||
},
|
||||
"sandbox.trustedBinaries": [
|
||||
"nasm",
|
||||
"make",
|
||||
"dos_port/tools/work_queue"
|
||||
]
|
||||
}
|
||||
{}
|
||||
|
||||
83
.agents/roles/dispatch.md
Normal file
83
.agents/roles/dispatch.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Role: Dispatch_Manager
|
||||
|
||||
**Model**: `gemini-3.1-pro` | **Settings**: `effort: high`
|
||||
|
||||
Top-level swarm coordinator. Reads the work queue, writes per-function tickets
|
||||
for Code Workers, verifies returned translations, marks jobs translated, and
|
||||
hands finished files to Integration and Docs agents.
|
||||
|
||||
Does **not** touch `complex`-category functions — those are left for Claude.
|
||||
|
||||
---
|
||||
|
||||
## Required Reading
|
||||
|
||||
Read these before writing any ticket:
|
||||
|
||||
| File | Why |
|
||||
|---|---|
|
||||
| `CLAUDE.md` | Register map, EBP memory model, DPMI gotchas, build conventions |
|
||||
| `docs/register_map.md` | Canonical SM83→x86 register assignments |
|
||||
| `docs/386_optimization_strategy.md` | Instruction selection rules for 386 targets |
|
||||
| `docs/bugs_and_glitches.md` | Which SM83 bugs to preserve and at what fix level |
|
||||
| `docs/glitch_safety.md` | Safe vs. dangerous glitches under DPMI |
|
||||
|
||||
Or load on demand:
|
||||
- `agy skill register-map` — compact SM83→x86 table
|
||||
- `agy skill bug-check` — BUG_FIX_LEVEL template
|
||||
- `agy skill glitch-escalation` — when to escalate
|
||||
|
||||
---
|
||||
|
||||
## Work Queue Interface
|
||||
|
||||
```sh
|
||||
dos_port/tools/work_queue status
|
||||
dos_port/tools/work_queue claim --agent Dispatch_Manager --count 5 --category simple
|
||||
dos_port/tools/work_queue fail --id <ID> --notes "reason"
|
||||
dos_port/tools/work_queue list --category simple --status needs_translation --limit 20
|
||||
dos_port/tools/work_queue pending-placement
|
||||
```
|
||||
|
||||
Run `dos_port/tools/work_queue --help` for full reference.
|
||||
|
||||
After a worker returns, verify the scratch file assembles:
|
||||
```sh
|
||||
nasm -f coff -o /dev/null dos_port/scratch/<id>__<label>.asm
|
||||
```
|
||||
Then call:
|
||||
```sh
|
||||
dos_port/tools/work_queue complete --id <ID> --scratch dos_port/scratch/<id>__<label>.asm --agent Dispatch_Manager
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ticket Format (sent to each Code Worker)
|
||||
|
||||
Each ticket must include:
|
||||
1. Pret source file path and the exact label to translate
|
||||
2. Target output file under `dos_port/src/` (verbatim mirror of pret path)
|
||||
3. Relevant rows from `docs/register_map.md` (copy verbatim)
|
||||
4. `gb_memmap.inc` constants used, with hex values pre-resolved
|
||||
5. Any `; BUG()` / `; GLITCH:` annotations from `docs/bugs_and_glitches.md`
|
||||
6. Exact `nasm -f coff -o /dev/null <file>` command to verify assembly
|
||||
7. Include lines to paste at the top (bare filenames only — see include rule)
|
||||
|
||||
**Include path rule** (copy into every ticket):
|
||||
```nasm
|
||||
%include "gb_memmap.inc" ; correct — NASM invoked from dos_port/ with -I include/
|
||||
%include "gb_macros.inc" ; correct
|
||||
|
||||
%include "dos_port/include/gb_memmap.inc" ; WRONG — breaks the build
|
||||
%include "include/gb_memmap.inc" ; WRONG — breaks the build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dispatch Rules
|
||||
|
||||
- Never assign two workers to the same output file simultaneously.
|
||||
- Only dispatch `simple`-category jobs. On hardware I/O, call `work_queue fail`.
|
||||
- Maximum 5 Code Workers active at once.
|
||||
- Workers use `agy skill` on demand — do not bulk-paste all docs into tickets.
|
||||
- After a worker returns, verify assembly before calling `work_queue complete`.
|
||||
39
.agents/roles/docs.md
Normal file
39
.agents/roles/docs.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Role: Docs_Commit_Agent
|
||||
|
||||
**Model**: `gemini-3.1-pro` | **Settings**: `effort: high`
|
||||
|
||||
Maintain documentation and execute git commits after each Integration Agent
|
||||
placement. Does not translate or place code.
|
||||
|
||||
---
|
||||
|
||||
## Required Reading
|
||||
|
||||
- `CLAUDE.md` — commit conventions, project phase context
|
||||
- `agy skill commit-format` — commit message format
|
||||
|
||||
---
|
||||
|
||||
## Translation Log Entries
|
||||
|
||||
For each function Integration Agent just placed, run:
|
||||
```sh
|
||||
dos_port/tools/work_queue translation-log-entry --id <ID>
|
||||
```
|
||||
|
||||
This returns JSON with a ready-to-append `entry` field. Append it verbatim to
|
||||
`docs/translation_log.md`. If worker left notes blank, fill in what you can
|
||||
infer from the diff before committing.
|
||||
|
||||
---
|
||||
|
||||
## Commit Conventions
|
||||
|
||||
- Subject ≤ 72 chars. Use `agy skill commit-format` for the trailer format.
|
||||
- Body: SM83 → x86 translation notes (register decisions, bug-fix level used).
|
||||
- Trailer: `Co-Authored-By: Gemini <noreply@google.com>` for swarm content.
|
||||
- Never commit: `.o`, `.orig`, `DUMP.BIN`, `FRAME.BIN`, `translation.db`,
|
||||
scratch files, or any file not changed by the current work unit.
|
||||
- `git add <exact files>` — never `git add -A` or `git add .`.
|
||||
- Never `--no-verify`.
|
||||
- Never push.
|
||||
71
.agents/roles/integration.md
Normal file
71
.agents/roles/integration.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Role: Integration_Agent
|
||||
|
||||
**Model**: `gemini-3.5-flash` | **Settings**: `effort: high`
|
||||
|
||||
Place translated functions into the correct `dos_port/src/` files so the build
|
||||
can see them. Add `%include` lines to aggregators, add Makefile rules where
|
||||
needed. That is the full scope of this role.
|
||||
|
||||
---
|
||||
|
||||
## Required Reading
|
||||
|
||||
Read before placing anything:
|
||||
- `CLAUDE.md` — linker section rules, build conventions
|
||||
- `docs/register_map.md` — register conventions (needed for diff review)
|
||||
- `agy skill path-map` — correct/wrong path table (load this first)
|
||||
|
||||
---
|
||||
|
||||
## Path Mapping Rule (CRITICAL)
|
||||
|
||||
The `dos_port/src/` path mirrors the pret source path one-to-one:
|
||||
**prepend `dos_port/src/` to the pret source path. Never rename, restructure,
|
||||
or drop prefix segments.**
|
||||
|
||||
| Pret source | Correct dos_port/src/ path |
|
||||
|---|---|
|
||||
| `engine/battle/experience.asm` | `dos_port/src/engine/battle/experience.asm` |
|
||||
| `engine/math/bcd.asm` | `dos_port/src/engine/math/bcd.asm` |
|
||||
| `engine/pokemon/bills_pc.asm` | `dos_port/src/engine/pokemon/bills_pc.asm` |
|
||||
| `engine/items/inventory.asm` | `dos_port/src/engine/items/inventory.asm` |
|
||||
| `engine/slots/slot_machine.asm` | `dos_port/src/engine/slots/slot_machine.asm` |
|
||||
| `home/math.asm` | `dos_port/src/home/math.asm` |
|
||||
|
||||
**WRONG — never do this:**
|
||||
|
||||
| Pret source | Wrong path |
|
||||
|---|---|
|
||||
| `engine/math/bcd.asm` | `dos_port/src/util/bcd.asm` |
|
||||
| `engine/pokemon/bills_pc.asm` | `dos_port/src/pokemon/bills_pc.asm` |
|
||||
| `engine/items/inventory.asm` | `dos_port/src/items/inventory.asm` |
|
||||
| `engine/slots/slot_machine.asm` | `dos_port/src/slots/slot_machine.asm` |
|
||||
|
||||
The `engine/` prefix is **never** dropped. `engine/math/` is **never** renamed to
|
||||
`util/`. If unsure, run `ls dos_port/src/` before writing.
|
||||
|
||||
---
|
||||
|
||||
## Integration Checklist (per function)
|
||||
|
||||
1. `dos_port/tools/work_queue pending-placement` — see what's ready.
|
||||
2. Read the manifest header in the scratch file for source origin.
|
||||
3. Derive destination: `dos_port/src/` + pret source path (path-map rule above).
|
||||
4. Copy file to destination. Scratch remains until session end (gitignored).
|
||||
5. Add `%include` in the appropriate aggregator file.
|
||||
6. If new compilation unit, add `$(OBJ)/name.o` to Makefile and append to `OBJS`.
|
||||
7. `nasm -f coff -o /dev/null <aggregator>` — must pass.
|
||||
8. `make -C dos_port` — must pass clean with no new warnings.
|
||||
9. `dos_port/tools/work_queue place --id <ID> --output dos_port/src/<path>`
|
||||
10. Hand diff to `Docs_Commit_Agent`.
|
||||
|
||||
---
|
||||
|
||||
## What This Agent MUST NOT Do
|
||||
|
||||
- **No live-graph wiring.** Do not edit any function reachable from
|
||||
`OverworldLoop`, `EnterMap`, `DelayFrame`, or their transitive callees to
|
||||
add calls to newly-placed code. Wiring is Claude Code only.
|
||||
- Do not touch `translated`-status files until `place` is called.
|
||||
- Do not modify wired files beyond adding `%include` to an aggregator.
|
||||
- **DO NOT WRITE PYTHON SCRIPTS TO EDIT FILES.** You have tools for this.
|
||||
69
.agents/roles/worker.md
Normal file
69
.agents/roles/worker.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Role: Code_Worker (×5 instances)
|
||||
|
||||
**Model**: `gemini-3.5-flash` | **Settings**: `effort: high`
|
||||
|
||||
Translate one SM83 function to x86 NASM 32-bit protected mode per ticket from
|
||||
Dispatch_Manager. One ticket per worker at a time. Write output to
|
||||
`dos_port/scratch/<id>__<label>.asm` only — never touch existing files.
|
||||
|
||||
---
|
||||
|
||||
## Required Reading (on demand — use `agy skill`)
|
||||
|
||||
Load these only when you need them:
|
||||
- `agy skill register-map` — SM83→x86 register table + EBP memory model
|
||||
- `agy skill bug-check` — BUG_FIX_LEVEL template and usage
|
||||
- `agy skill glitch-escalation` — when to stop and report a $FF__ hit
|
||||
- `agy skill 386-checklist` — instruction selection checklist
|
||||
|
||||
Full references (read if ticket is ambiguous):
|
||||
- `docs/register_map.md`, `docs/bugs_and_glitches.md`, `docs/glitch_safety.md`
|
||||
|
||||
---
|
||||
|
||||
## Scratch File Format
|
||||
|
||||
Get the manifest header:
|
||||
```sh
|
||||
dos_port/tools/work_queue manifest --id <ID>
|
||||
```
|
||||
|
||||
Write that header verbatim at the top of `dos_port/scratch/<id>__<label>.asm`,
|
||||
**fill in the four WORKER NOTES fields**, then append translated NASM beneath
|
||||
the `CODE BELOW` line. Example filled notes:
|
||||
|
||||
```nasm
|
||||
; registers : HL→ESI for exp table ptr, A→AL, BC→BX for growth rate
|
||||
; hflag : not involved
|
||||
; bug_tags : BUG(cosmetic): overflow in EXP display — pret ref: experience.asm:L42
|
||||
; notes : used imul for exp formula; SM83 used 16-bit mul via DE pair
|
||||
```
|
||||
|
||||
Leave unfilled placeholders (text in parentheses) if they genuinely don't apply.
|
||||
`work_queue complete` parses these fields automatically.
|
||||
|
||||
---
|
||||
|
||||
## Mandatory Checklist
|
||||
|
||||
1. Read the exact pret source label from the ticket. Read surrounding context.
|
||||
2. Check ticket for `; BUG()` annotations. Apply `; BUG(level):` block at site.
|
||||
3. If function involves a known glitch, load `agy skill glitch-escalation`.
|
||||
4. Run `dos_port/tools/work_queue manifest --id <ID>` and write the header.
|
||||
5. Translate under the header. Use `[EBP + constant]` for all GB memory.
|
||||
Emit `; TODO-HW:` for any `$FF__` register access.
|
||||
6. Fill in the four WORKER NOTES fields before finishing.
|
||||
7. Run `nasm -f coff -o /dev/null dos_port/scratch/<id>__<label>.asm` — must pass.
|
||||
8. Return scratch path and nasm stdout to Dispatch_Manager.
|
||||
|
||||
---
|
||||
|
||||
## Hard Limits
|
||||
|
||||
- No spawning sub-agents.
|
||||
- No touching graphics, VGA, OAM, VRAM, audio, or joypad code.
|
||||
- Write only to `dos_port/scratch/` — do not modify any existing file.
|
||||
- Do not add `%include` lines or Makefile rules (Integration Agent's job).
|
||||
- `call` inside translated function = fine. `call` in an existing file = never.
|
||||
- Include lines use bare names only: `%include "gb_memmap.inc"`.
|
||||
- If you hit a `$FF__` register not in the ticket, stop and report immediately.
|
||||
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"antigravity.security.reviewPolicy": "proceed-in-sandbox",
|
||||
"enableTerminalSandbox": true,
|
||||
"antigravity.security.strictMode": false,
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"command(grep)",
|
||||
"command(grep_search)",
|
||||
"command(nasm)",
|
||||
"command(cat)",
|
||||
"command(dos_port/tools/work_queue)",
|
||||
"command(dos_port/tools/build_index)",
|
||||
"command(make)",
|
||||
"command(bash)",
|
||||
"command(dos_port/tools/process_placements)",
|
||||
"command(sed)",
|
||||
"command(awk)",
|
||||
"command(ls)",
|
||||
"command(make compare)",
|
||||
"command(git status)",
|
||||
"command(git add)",
|
||||
"command(git commit)",
|
||||
"command(find)"
|
||||
"command(*)",
|
||||
"function(*)"
|
||||
],
|
||||
"ask": [],
|
||||
"deny": [
|
||||
"command(dd *)",
|
||||
"command(dd)",
|
||||
"command(git push *)",
|
||||
"command(git push)"
|
||||
]
|
||||
}
|
||||
},
|
||||
"sandbox.trustedBinaries": [
|
||||
"nasm",
|
||||
"make",
|
||||
"dos_port/tools/work_queue",
|
||||
"dos_port/tools/build_index"
|
||||
]
|
||||
}
|
||||
|
||||
46
.agents/skills/386-checklist.md
Normal file
46
.agents/skills/386-checklist.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Skill: 386-checklist
|
||||
|
||||
Fast instruction selection checklist for 386+ NASM translations.
|
||||
|
||||
## Memory Access
|
||||
|
||||
- `[EBP + constant]` for ALL GB memory — never use raw GB addresses
|
||||
- Use `movzx eax, byte [ebp + addr]` to load 8-bit value into 32-bit reg
|
||||
- Use `movzx eax, word [ebp + addr]` for 16-bit
|
||||
- For 16-bit GB HL register pair: use ESI for pointer, `movzx esi, word [...]`
|
||||
|
||||
## Arithmetic
|
||||
|
||||
- 8-bit add/sub: operate on AL/BL/etc., let carry propagate naturally
|
||||
- 16-bit GB arithmetic (BC, DE, HL): use 16-bit x86 BX/DX/SI with `movzx` after
|
||||
- Multiply: `imul eax, ecx, N` preferred over shift sequences for clarity
|
||||
- Divide: `div` or `idiv` with zero-extension for unsigned GB divisions
|
||||
|
||||
## Flags
|
||||
|
||||
- Z flag: direct — `test al, al` / `cmp al, bl` works identically
|
||||
- C flag: direct — `jc`/`jnc` works
|
||||
- H flag: lazy — only compute `[hf_shadow]` if DAA or CPL follows
|
||||
- N flag: implicit in instruction choice (SUB sets, ADD clears)
|
||||
|
||||
## Common Patterns
|
||||
|
||||
```nasm
|
||||
; GB: ld a, [hl] → movzx eax, byte [ebp + esi]
|
||||
; GB: ld [hl], a → mov [ebp + esi], al
|
||||
; GB: inc hl → inc esi
|
||||
; GB: ld hl, NN → mov esi, NN
|
||||
; GB: push bc → push ebx
|
||||
; GB: pop bc → pop ebx
|
||||
; GB: call Label → call Label
|
||||
; GB: ret → ret
|
||||
; GB: ret z → jz .done (inline check before call)
|
||||
; GB: jr nz, .label → jnz .label
|
||||
```
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not use `[ESP + N]` to access GB memory
|
||||
- Do not use segment overrides (DS:, ES:) — flat model only
|
||||
- Do not use `loop` instruction — it only tests CX (16-bit under 32-bit mode)
|
||||
- Do not use FAR jumps/calls
|
||||
47
.agents/skills/bug-check.md
Normal file
47
.agents/skills/bug-check.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Skill: bug-check
|
||||
|
||||
BUG_FIX_LEVEL convention for this port.
|
||||
|
||||
## Levels
|
||||
|
||||
- `BUG_FIX_LEVEL 0` — original buggy behavior (default / no flag)
|
||||
- `BUG_FIX_LEVEL 1` — critical bugs only (`/FIXCRIT` flag on PKMN.EXE)
|
||||
- `BUG_FIX_LEVEL 2` — all bugs including cosmetic (`/FIXALL` flag)
|
||||
|
||||
## Template
|
||||
|
||||
```nasm
|
||||
; BUG(critical): <what goes wrong> — pret ref: <file>:<label>, bugs_and_glitches.md#L<N>
|
||||
%if BUG_FIX_LEVEL >= 1
|
||||
; corrected implementation
|
||||
%else
|
||||
; original buggy behavior (verbatim from SM83)
|
||||
%endif
|
||||
```
|
||||
|
||||
For cosmetic/non-critical bugs:
|
||||
```nasm
|
||||
; BUG(cosmetic): <what goes wrong> — pret ref: <file>:<label>
|
||||
%if BUG_FIX_LEVEL >= 2
|
||||
; corrected implementation
|
||||
%else
|
||||
; original behavior
|
||||
%endif
|
||||
```
|
||||
|
||||
## When to Apply
|
||||
|
||||
1. Check ticket for explicit `; BUG()` annotations — apply if present.
|
||||
2. If unsure whether something is a bug, check `docs/bugs_and_glitches.md`.
|
||||
3. If the function has NO known bugs, emit no BUG block.
|
||||
4. Never invent BUG blocks for behavior not documented in bugs_and_glitches.md.
|
||||
|
||||
## Glitch Comment
|
||||
|
||||
For intentional exploitable glitches:
|
||||
```nasm
|
||||
; GLITCH: <name> — <one-line description>
|
||||
; Safety: safe under DPMI (bounded) | unsafe on bare HW if ACE reachable
|
||||
```
|
||||
|
||||
Do NOT use the `%if` block for glitches — glitches are always preserved.
|
||||
44
.agents/skills/commit-format.md
Normal file
44
.agents/skills/commit-format.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Skill: commit-format
|
||||
|
||||
Git commit message conventions for swarm-generated translations.
|
||||
|
||||
## Format
|
||||
|
||||
```
|
||||
Translate <Label>[, <Label2>...]: <one-line summary>
|
||||
|
||||
SM83→x86 notes:
|
||||
- <register decisions, e.g. "HL→ESI for table ptr">
|
||||
- <H-flag involvement, e.g. "H flag not used">
|
||||
- <BUG_FIX_LEVEL applied, e.g. "BUG(cosmetic) block at RestorePPAmount">
|
||||
- <any notable deviation from literal translation>
|
||||
|
||||
Co-Authored-By: Gemini <noreply@google.com>
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- Subject line ≤ 72 characters
|
||||
- Use present tense: "Translate", not "Translated"
|
||||
- Body SM83 notes are mandatory — do not omit
|
||||
- Trailer is `Co-Authored-By: Gemini <noreply@google.com>` for all swarm commits
|
||||
- Never commit: `.o`, `.orig`, `DUMP.BIN`, `FRAME.BIN`, `translation.db`,
|
||||
scratch files, unrelated editor files
|
||||
- Stage only the files changed by this work unit: `git add <exact paths>`
|
||||
- Never `git add -A` or `git add .`
|
||||
- Never `--no-verify`
|
||||
- Never push
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
Translate CalcExperience, GainExperience: battle experience formulas
|
||||
|
||||
SM83→x86 notes:
|
||||
- HL→ESI for exp table ptr, BC→BX for growth rate pair
|
||||
- H flag not involved
|
||||
- imul used for exp formula (SM83 used 16-bit mul via DE pair)
|
||||
- BUG(cosmetic) block at exp overflow display (BUG_FIX_LEVEL >= 2)
|
||||
|
||||
Co-Authored-By: Gemini <noreply@google.com>
|
||||
```
|
||||
37
.agents/skills/glitch-escalation.md
Normal file
37
.agents/skills/glitch-escalation.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Skill: glitch-escalation
|
||||
|
||||
When to stop and escalate instead of translating.
|
||||
|
||||
## Escalation Triggers
|
||||
|
||||
Stop immediately and report to Dispatch_Manager if the function:
|
||||
|
||||
1. **Accesses a `$FF__` hardware register** not listed in your ticket.
|
||||
These are I/O boundaries — see `CLAUDE.md` "Hardware I/O Boundary" section.
|
||||
Do NOT guess what the register does. Report the register address.
|
||||
|
||||
2. **Calls a graphics or audio routine** not in your ticket (any function in
|
||||
`home/audio.asm`, `home/lcd.asm`, `engine/gfx/`, etc.).
|
||||
|
||||
3. **Accesses `$FE00`** (OAM) or `$FF40–$FF4B` (LCDC, palettes, DMA).
|
||||
|
||||
4. **Involves a glitch rated "unsafe"** in `docs/glitch_safety.md`.
|
||||
Unsafe = can trigger ACE or write to arbitrary memory under DPMI.
|
||||
|
||||
## How to Escalate
|
||||
|
||||
```sh
|
||||
dos_port/tools/work_queue recategorize --id <ID> --category complex --notes "reason: $FF4X access"
|
||||
dos_port/tools/work_queue fail --id <ID> --notes "escalated to complex: <reason>"
|
||||
```
|
||||
|
||||
Return to Dispatch_Manager with:
|
||||
- The register or function that triggered escalation
|
||||
- The line number in the pret source
|
||||
|
||||
## Safe to Translate Without Escalation
|
||||
|
||||
- Any `$FF` address that is a GB RAM address (`$FF80–$FFFE` = HRAM):
|
||||
these are regular memory, not I/O. Map via `[EBP + hram_offset]`.
|
||||
- `call` to another simple function listed in `AGENTS.md` simple category.
|
||||
- Bit manipulation of GB RAM bytes (status flags, counters, etc.).
|
||||
40
.agents/skills/path-map.md
Normal file
40
.agents/skills/path-map.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Skill: path-map
|
||||
|
||||
Correct/wrong path table for placing translated files.
|
||||
|
||||
## Rule
|
||||
|
||||
`dos_port/src/` output path = `dos_port/src/` + pret source path verbatim.
|
||||
Never drop prefix segments. Never rename directories.
|
||||
|
||||
## Correct Examples
|
||||
|
||||
| Pret source | dos_port/src/ destination |
|
||||
|---|---|
|
||||
| `engine/battle/experience.asm` | `dos_port/src/engine/battle/experience.asm` |
|
||||
| `engine/math/bcd.asm` | `dos_port/src/engine/math/bcd.asm` |
|
||||
| `engine/math/multiply_divide.asm` | `dos_port/src/engine/math/multiply_divide.asm` |
|
||||
| `engine/pokemon/bills_pc.asm` | `dos_port/src/engine/pokemon/bills_pc.asm` |
|
||||
| `engine/items/inventory.asm` | `dos_port/src/engine/items/inventory.asm` |
|
||||
| `engine/items/get_bag_item_quantity.asm` | `dos_port/src/engine/items/get_bag_item_quantity.asm` |
|
||||
| `engine/slots/slot_machine.asm` | `dos_port/src/engine/slots/slot_machine.asm` |
|
||||
| `engine/debug/debug_party.asm` | `dos_port/src/engine/debug/debug_party.asm` |
|
||||
| `home/math.asm` | `dos_port/src/home/math.asm` |
|
||||
| `home/copy2.asm` | `dos_port/src/home/copy2.asm` |
|
||||
|
||||
## Wrong (DO NOT USE)
|
||||
|
||||
| Pret source | Wrong path |
|
||||
|---|---|
|
||||
| `engine/math/bcd.asm` | `dos_port/src/util/bcd.asm` ← drops engine/, renames |
|
||||
| `engine/math/multiply_divide.asm` | `dos_port/src/util/multiply_divide.asm` |
|
||||
| `engine/pokemon/bills_pc.asm` | `dos_port/src/pokemon/bills_pc.asm` ← drops engine/ |
|
||||
| `engine/items/inventory.asm` | `dos_port/src/items/inventory.asm` ← drops engine/ |
|
||||
| `engine/slots/slot_machine.asm` | `dos_port/src/slots/slot_machine.asm` |
|
||||
| `engine/debug/debug_party.asm` | `dos_port/src/debug/debug_party.asm` |
|
||||
| `home/copy2.asm` | `dos_port/src/home/copy.asm` ← wrong filename |
|
||||
|
||||
## Exception
|
||||
|
||||
Manually-written Claude Code files may use different names. These are listed in
|
||||
`dos_port/tools/build_index` in `VERIFIED_MAP` and are not governed by this rule.
|
||||
39
.agents/skills/register-map.md
Normal file
39
.agents/skills/register-map.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Skill: register-map
|
||||
|
||||
SM83 → x86 register mapping for this port.
|
||||
|
||||
## Register Table
|
||||
|
||||
| SM83 | x86 | Notes |
|
||||
|------|-----|-------|
|
||||
| A | AL | Accumulator |
|
||||
| F: Z, C | EFLAGS ZF, CF | Direct |
|
||||
| F: H | `[hf_shadow]` | BSS byte; lazy — only update where DAA/CPL consume H |
|
||||
| F: N | (implicit) | Tracked via instruction choice, not a flag |
|
||||
| BC | BX | B = BH, C = BL |
|
||||
| DE | DX | D = DH, E = DL |
|
||||
| HL | ESI | Full 32-bit, used for flat addressing |
|
||||
| SP | ESP | Direct; mind calling convention |
|
||||
| — | EBP | Fixed base → emulated GB address space |
|
||||
| — | EDI | Secondary pointer / blit destination |
|
||||
| — | ECX | Loop counter / scratch |
|
||||
|
||||
## EBP Memory Model
|
||||
|
||||
`EBP` = base of ~96 KB DPMI allocation (64 KB GB space + extras).
|
||||
All emulated GB memory: `[EBP + constant]` where constants come from
|
||||
`dos_port/include/gb_memmap.inc`. Never use raw GB addresses — always offset
|
||||
from EBP. Example:
|
||||
|
||||
```nasm
|
||||
; GB: ld a, [wCurItem] → x86: mov al, [ebp + wCurItem]
|
||||
; GB: ld [hl], a → x86: mov [ebp + esi], al
|
||||
```
|
||||
|
||||
## Preferred 386 Instructions
|
||||
|
||||
- `movzx`/`movsx` for zero/sign extension (never use AND to zero-extend)
|
||||
- `imul reg, reg, imm` for index math
|
||||
- `lea` for flags-preserving address computation
|
||||
- `rep stos`/`rep movs` for block fills/copies
|
||||
- Never use 32-bit operands for 8-bit/16-bit GB values without masking
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
build_index.py — Scan the pret/pokeyellow GB source (home/ and engine/) and
|
||||
build_index — Scan the pret/pokeyellow GB source (home/ and engine/) and
|
||||
build (or rebuild) the translation work-queue database at dos_port/tools/translation.db.
|
||||
|
||||
Usage:
|
||||
python3 dos_port/tools/build_index.py [--rebuild]
|
||||
dos_port/tools/build_index [--rebuild]
|
||||
|
||||
--rebuild Drop and recreate all tables before inserting. Without this flag,
|
||||
only rows whose source_file is not already present are inserted,
|
||||
@@ -18,18 +18,14 @@ import sqlite3
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# ── Repo root is two levels above this script ──────────────────────────────────
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO_ROOT = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..'))
|
||||
DB_PATH = os.path.join(SCRIPT_DIR, 'translation.db')
|
||||
|
||||
# ── Categorisation tables ───────────────────────────────────────────────────────
|
||||
# Paths are relative to REPO_ROOT. Order: first match wins (checked against the
|
||||
# full relative path of each .asm file using str.startswith / 'in' membership).
|
||||
# Paths are relative to REPO_ROOT.
|
||||
|
||||
# Files whose ENTIRE CONTENT is simple (no HW I/O, no gfx, no audio).
|
||||
SIMPLE_FILES = {
|
||||
# home/ utility / data routines
|
||||
'home/math.asm',
|
||||
'home/compare.asm',
|
||||
'home/copy.asm',
|
||||
@@ -51,12 +47,10 @@ SIMPLE_FILES = {
|
||||
'home/hidden_events.asm',
|
||||
'home/print_bcd.asm',
|
||||
'home/print_num.asm',
|
||||
# engine/flag / math
|
||||
'engine/flag_action.asm',
|
||||
'engine/math/bcd.asm',
|
||||
'engine/math/multiply_divide.asm',
|
||||
'engine/math/random.asm',
|
||||
# engine/battle — pure-logic routines only
|
||||
'engine/battle/decrement_pp.asm',
|
||||
'engine/battle/experience.asm',
|
||||
'engine/battle/misc.asm',
|
||||
@@ -67,7 +61,6 @@ SIMPLE_FILES = {
|
||||
'engine/battle/save_trainer_name.asm',
|
||||
'engine/battle/read_trainer_party.asm',
|
||||
'engine/battle/get_trainer_name.asm',
|
||||
# engine/battle/move_effects — all pure stat manipulation
|
||||
'engine/battle/move_effects/conversion.asm',
|
||||
'engine/battle/move_effects/drain_hp.asm',
|
||||
'engine/battle/move_effects/focus_energy.asm',
|
||||
@@ -82,14 +75,12 @@ SIMPLE_FILES = {
|
||||
'engine/battle/move_effects/reflect_light_screen.asm',
|
||||
'engine/battle/move_effects/substitute.asm',
|
||||
'engine/battle/move_effects/transform.asm',
|
||||
# engine/items — data lookup / arithmetic only
|
||||
'engine/items/get_bag_item_quantity.asm',
|
||||
'engine/items/inventory.asm',
|
||||
'engine/items/subtract_paid_money.asm',
|
||||
'engine/items/tm_prices.asm',
|
||||
'engine/items/tms.asm',
|
||||
'engine/items/tmhm.asm',
|
||||
# engine/pokemon — data / formula
|
||||
'engine/pokemon/evos_moves.asm',
|
||||
'engine/pokemon/experience.asm',
|
||||
'engine/pokemon/set_types.asm',
|
||||
@@ -97,7 +88,6 @@ SIMPLE_FILES = {
|
||||
'engine/pokemon/add_mon.asm',
|
||||
'engine/pokemon/remove_mon.asm',
|
||||
'engine/pokemon/load_mon_data.asm',
|
||||
# engine/overworld — geometry / flag checks only
|
||||
'engine/overworld/wild_mons.asm',
|
||||
'engine/overworld/daycare_exp.asm',
|
||||
'engine/overworld/sprite_collisions.asm',
|
||||
@@ -105,78 +95,80 @@ SIMPLE_FILES = {
|
||||
'engine/overworld/is_player_just_outside_map.asm',
|
||||
'engine/overworld/clear_variables.asm',
|
||||
'engine/overworld/specific_script_flags.asm',
|
||||
# engine/events — give/heal/flag events (no gfx)
|
||||
'engine/events/give_pokemon.asm',
|
||||
'engine/events/heal_party.asm',
|
||||
'engine/events/card_key.asm',
|
||||
# engine/debug
|
||||
'engine/debug/debug_menu.asm',
|
||||
'engine/debug/debug_party.asm',
|
||||
}
|
||||
|
||||
# Directory prefixes whose every file is simple
|
||||
SIMPLE_PREFIXES = (
|
||||
'engine/events/hidden_events/',
|
||||
)
|
||||
|
||||
# ── Files whose translations are already in dos_port/src and were written by
|
||||
# Claude (trusted, verified in DOSBox-X). Status = 'complete'.
|
||||
COMPLETE_MAP = {
|
||||
# pret source path → dos_port output path
|
||||
'home/joypad.asm': 'dos_port/src/input/joypad.asm',
|
||||
'home/load_font.asm': 'dos_port/src/gfx/load_font.asm',
|
||||
'home/copy.asm': 'dos_port/src/util/copy_data.asm',
|
||||
'home/init.asm': 'dos_port/src/init/init.asm',
|
||||
'engine/movie/title_yellow.asm': 'dos_port/src/movie/title.asm',
|
||||
'engine/gfx/sprite_oam.asm': 'dos_port/src/gfx/sprite_oam.asm',
|
||||
'engine/overworld/movement.asm': 'dos_port/src/overworld/movement.asm',
|
||||
# ── Files verified working in DOSBox-X (Claude Code sessions).
|
||||
# Status = 'verified'. These may use non-mirrored paths where Claude deliberately
|
||||
# chose a different name (e.g., home/copy.asm → src/util/copy_data.asm).
|
||||
# Rule: label must exist in output file; if absent, falls back to needs_translation.
|
||||
VERIFIED_MAP = {
|
||||
# pret source → dos_port output (repo-relative)
|
||||
'home/joypad.asm': 'dos_port/src/input/joypad.asm',
|
||||
'home/load_font.asm': 'dos_port/src/gfx/load_font.asm',
|
||||
'home/copy.asm': 'dos_port/src/util/copy_data.asm',
|
||||
'home/init.asm': 'dos_port/src/init/init.asm',
|
||||
'engine/movie/title_yellow.asm': 'dos_port/src/movie/title.asm',
|
||||
'engine/gfx/sprite_oam.asm': 'dos_port/src/gfx/sprite_oam.asm',
|
||||
'engine/overworld/movement.asm': 'dos_port/src/overworld/movement.asm',
|
||||
}
|
||||
|
||||
# ── Files that exist in dos_port/src but were AI-generated and are unverified.
|
||||
# Status = 'unverified'. The integration agent must review before wiring.
|
||||
UNVERIFIED_MAP = {
|
||||
'home/math.asm': 'dos_port/src/home/math.asm',
|
||||
'home/compare.asm': 'dos_port/src/home/compare.asm',
|
||||
'home/random.asm': 'dos_port/src/home/random.asm',
|
||||
'home/copy2.asm': 'dos_port/src/home/copy.asm',
|
||||
'home/array.asm': 'dos_port/src/home/array.asm',
|
||||
'home/count_set_bits.asm': 'dos_port/src/home/count_set_bits.asm',
|
||||
'engine/math/bcd.asm': 'dos_port/src/util/bcd.asm',
|
||||
'engine/math/multiply_divide.asm': 'dos_port/src/util/multiply_divide.asm',
|
||||
'engine/math/random.asm': 'dos_port/src/util/random.asm',
|
||||
'engine/battle/decrement_pp.asm': 'dos_port/src/engine/battle/decrement_pp.asm',
|
||||
'engine/battle/experience.asm': 'dos_port/src/engine/battle/experience.asm',
|
||||
'engine/flag_action.asm': 'dos_port/src/engine/flag_action.asm',
|
||||
'engine/items/get_bag_item_quantity.asm':'dos_port/src/items/get_bag_item_quantity.asm',
|
||||
'engine/items/inventory.asm': 'dos_port/src/items/inventory.asm',
|
||||
'engine/items/subtract_paid_money.asm': 'dos_port/src/items/subtract_paid_money.asm',
|
||||
'engine/items/tm_prices.asm': 'dos_port/src/items/tm_prices.asm',
|
||||
'engine/items/tms.asm': 'dos_port/src/engine/items/tms.asm',
|
||||
'engine/items/tmhm.asm': 'dos_port/src/engine/items/tmhm.asm',
|
||||
'engine/items/item_effects.asm': 'dos_port/src/engine/items/item_effects.asm',
|
||||
'engine/items/itemfinder.asm': 'dos_port/src/items/itemfinder.asm',
|
||||
'engine/items/super_rod.asm': 'dos_port/src/items/super_rod.asm',
|
||||
'engine/items/town_map.asm': 'dos_port/src/engine/items/town_map.asm',
|
||||
'engine/pokemon/evos_moves.asm': 'dos_port/src/engine/pokemon/evos_moves.asm',
|
||||
'engine/pokemon/set_types.asm': 'dos_port/src/engine/pokemon/set_types.asm',
|
||||
'engine/pokemon/status_ailments.asm': 'dos_port/src/engine/pokemon/status_ailments.asm',
|
||||
'engine/pokemon/add_mon.asm': 'dos_port/src/engine/pokemon/add_mon.asm',
|
||||
'engine/pokemon/remove_mon.asm': 'dos_port/src/pokemon/remove_mon.asm',
|
||||
'engine/pokemon/load_mon_data.asm': 'dos_port/src/engine/pokemon/load_mon_data.asm',
|
||||
'engine/pokemon/experience.asm': 'dos_port/src/pokemon/experience.asm',
|
||||
'engine/pokemon/bills_pc.asm': 'dos_port/src/pokemon/bills_pc.asm',
|
||||
'engine/menus/pc.asm': 'dos_port/src/engine/menus/pc.asm',
|
||||
'engine/menus/save.asm': 'dos_port/src/engine/menus/save.asm',
|
||||
'engine/menus/swap_items.asm': 'dos_port/src/engine/menus/swap_items.asm',
|
||||
'engine/menus/text_box.asm': 'dos_port/src/engine/menus/text_box.asm',
|
||||
'engine/pikachu/pikachu_status.asm': 'dos_port/src/engine/pikachu/pikachu_status.asm',
|
||||
'engine/slots/slot_machine.asm': 'dos_port/src/slots/slot_machine.asm',
|
||||
'engine/debug/debug_party.asm': 'dos_port/src/debug/debug_party.asm',
|
||||
'engine/predefs.asm': 'dos_port/src/engine/predefs.asm',
|
||||
# ── Files translated by the swarm, in dos_port/src/, not yet verified in DOSBox-X.
|
||||
# Status = 'translated'. Path rule: always `dos_port/src/` + pret source path
|
||||
# verbatim. Never drop engine/, never rename paths.
|
||||
# Rule: label must exist in output file; if absent, falls back to needs_translation.
|
||||
TRANSLATED_MAP = {
|
||||
# pret source → dos_port output (repo-relative; one-to-one mirroring)
|
||||
'home/math.asm': 'dos_port/src/home/math.asm',
|
||||
'home/compare.asm': 'dos_port/src/home/compare.asm',
|
||||
'home/random.asm': 'dos_port/src/home/random.asm',
|
||||
'home/copy2.asm': 'dos_port/src/home/copy2.asm',
|
||||
'home/array.asm': 'dos_port/src/home/array.asm',
|
||||
'home/count_set_bits.asm': 'dos_port/src/home/count_set_bits.asm',
|
||||
'engine/math/bcd.asm': 'dos_port/src/engine/math/bcd.asm',
|
||||
'engine/math/multiply_divide.asm': 'dos_port/src/engine/math/multiply_divide.asm',
|
||||
'engine/math/random.asm': 'dos_port/src/engine/math/random.asm',
|
||||
'engine/battle/decrement_pp.asm': 'dos_port/src/engine/battle/decrement_pp.asm',
|
||||
'engine/battle/experience.asm': 'dos_port/src/engine/battle/experience.asm',
|
||||
'engine/flag_action.asm': 'dos_port/src/engine/flag_action.asm',
|
||||
'engine/items/get_bag_item_quantity.asm': 'dos_port/src/engine/items/get_bag_item_quantity.asm',
|
||||
'engine/items/inventory.asm': 'dos_port/src/engine/items/inventory.asm',
|
||||
'engine/items/subtract_paid_money.asm': 'dos_port/src/engine/items/subtract_paid_money.asm',
|
||||
'engine/items/tm_prices.asm': 'dos_port/src/engine/items/tm_prices.asm',
|
||||
'engine/items/tms.asm': 'dos_port/src/engine/items/tms.asm',
|
||||
'engine/items/tmhm.asm': 'dos_port/src/engine/items/tmhm.asm',
|
||||
'engine/items/item_effects.asm': 'dos_port/src/engine/items/item_effects.asm',
|
||||
'engine/items/itemfinder.asm': 'dos_port/src/engine/items/itemfinder.asm',
|
||||
'engine/items/super_rod.asm': 'dos_port/src/engine/items/super_rod.asm',
|
||||
'engine/items/town_map.asm': 'dos_port/src/engine/items/town_map.asm',
|
||||
'engine/pokemon/evos_moves.asm': 'dos_port/src/engine/pokemon/evos_moves.asm',
|
||||
'engine/pokemon/set_types.asm': 'dos_port/src/engine/pokemon/set_types.asm',
|
||||
'engine/pokemon/status_ailments.asm': 'dos_port/src/engine/pokemon/status_ailments.asm',
|
||||
'engine/pokemon/add_mon.asm': 'dos_port/src/engine/pokemon/add_mon.asm',
|
||||
'engine/pokemon/remove_mon.asm': 'dos_port/src/engine/pokemon/remove_mon.asm',
|
||||
'engine/pokemon/load_mon_data.asm': 'dos_port/src/engine/pokemon/load_mon_data.asm',
|
||||
'engine/pokemon/experience.asm': 'dos_port/src/engine/pokemon/experience.asm',
|
||||
'engine/pokemon/bills_pc.asm': 'dos_port/src/engine/pokemon/bills_pc.asm',
|
||||
'engine/menus/pc.asm': 'dos_port/src/engine/menus/pc.asm',
|
||||
'engine/menus/save.asm': 'dos_port/src/engine/menus/save.asm',
|
||||
'engine/menus/swap_items.asm': 'dos_port/src/engine/menus/swap_items.asm',
|
||||
'engine/menus/text_box.asm': 'dos_port/src/engine/menus/text_box.asm',
|
||||
'engine/pikachu/pikachu_status.asm': 'dos_port/src/engine/pikachu/pikachu_status.asm',
|
||||
'engine/slots/slot_machine.asm': 'dos_port/src/engine/slots/slot_machine.asm',
|
||||
'engine/debug/debug_party.asm': 'dos_port/src/engine/debug/debug_party.asm',
|
||||
'engine/predefs.asm': 'dos_port/src/engine/predefs.asm',
|
||||
}
|
||||
|
||||
# ── Label extraction ────────────────────────────────────────────────────────────
|
||||
# Match non-local, non-numeric labels at column 0 in RGBDS source.
|
||||
# ── Label helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
LABEL_RE = re.compile(r'^([A-Za-z_][A-Za-z0-9_]*):{1,2}')
|
||||
|
||||
def extract_labels(filepath):
|
||||
@@ -191,22 +183,39 @@ def extract_labels(filepath):
|
||||
pass
|
||||
return labels
|
||||
|
||||
def label_exists_in_file(label, filepath):
|
||||
"""Return True if 'label:' or 'label::' appears at column 0 in filepath."""
|
||||
full = os.path.join(REPO_ROOT, filepath)
|
||||
if not os.path.exists(full):
|
||||
return False
|
||||
pattern = re.compile(r'^' + re.escape(label) + r':{1,2}', re.MULTILINE)
|
||||
try:
|
||||
with open(full, encoding='utf-8', errors='replace') as fh:
|
||||
return bool(pattern.search(fh.read()))
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
# ── Schema ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS functions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
source_file TEXT NOT NULL,
|
||||
category TEXT NOT NULL CHECK(category IN ('simple','complex')),
|
||||
status TEXT NOT NULL DEFAULT 'needs_translation'
|
||||
CHECK(status IN
|
||||
('needs_translation','in_progress',
|
||||
'unverified','complete','skip')),
|
||||
output_file TEXT,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
source_file TEXT NOT NULL,
|
||||
category TEXT NOT NULL CHECK(category IN ('simple','complex')),
|
||||
status TEXT NOT NULL DEFAULT 'needs_translation'
|
||||
CHECK(status IN (
|
||||
'needs_translation','in_progress',
|
||||
'translated','wired','verified','skip',
|
||||
'complete','unverified'
|
||||
)),
|
||||
output_file TEXT,
|
||||
scratch_file TEXT,
|
||||
assigned_agent TEXT,
|
||||
notes TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now','utc')),
|
||||
updated_at TEXT DEFAULT (datetime('now','utc'))
|
||||
notes TEXT,
|
||||
translation_notes TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now','utc')),
|
||||
updated_at TEXT DEFAULT (datetime('now','utc'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_fn_status ON functions(status);
|
||||
@@ -225,7 +234,6 @@ CREATE TABLE IF NOT EXISTS translation_log (
|
||||
"""
|
||||
|
||||
def categorise(rel_path):
|
||||
"""Return 'simple' or 'complex' for a repo-relative .asm path."""
|
||||
norm = rel_path.replace('\\', '/')
|
||||
if norm in SIMPLE_FILES:
|
||||
return 'simple'
|
||||
@@ -234,16 +242,27 @@ def categorise(rel_path):
|
||||
return 'simple'
|
||||
return 'complex'
|
||||
|
||||
def status_and_output(rel_path):
|
||||
def status_and_output(rel_path, label):
|
||||
"""Return (status, output_path) for this label.
|
||||
|
||||
For verified/translated maps: only set that status if the label actually
|
||||
exists in the output file. If the file is missing or the label isn't found,
|
||||
fall back to needs_translation so the DB reflects reality.
|
||||
"""
|
||||
norm = rel_path.replace('\\', '/')
|
||||
if norm in COMPLETE_MAP:
|
||||
return 'complete', COMPLETE_MAP[norm]
|
||||
if norm in UNVERIFIED_MAP:
|
||||
return 'unverified', UNVERIFIED_MAP[norm]
|
||||
if norm in VERIFIED_MAP:
|
||||
out = VERIFIED_MAP[norm]
|
||||
if label_exists_in_file(label, out):
|
||||
return 'verified', out
|
||||
return 'needs_translation', None
|
||||
if norm in TRANSLATED_MAP:
|
||||
out = TRANSLATED_MAP[norm]
|
||||
if label_exists_in_file(label, out):
|
||||
return 'translated', out
|
||||
return 'needs_translation', None
|
||||
return 'needs_translation', None
|
||||
|
||||
def scan_source_files():
|
||||
"""Yield (rel_path, [label, ...]) for every .asm in home/ and engine/."""
|
||||
for top in ('home', 'engine'):
|
||||
top_dir = os.path.join(REPO_ROOT, top)
|
||||
if not os.path.isdir(top_dir):
|
||||
@@ -265,14 +284,16 @@ def build(rebuild=False):
|
||||
|
||||
cur.executescript(SCHEMA)
|
||||
|
||||
# Migrate legacy status names from old schema
|
||||
cur.execute("UPDATE functions SET status='translated' WHERE status='complete'")
|
||||
cur.execute("UPDATE functions SET status='needs_translation' WHERE status='unverified'")
|
||||
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
inserted = skipped = 0
|
||||
|
||||
for rel_path, labels in scan_source_files():
|
||||
cat = categorise(rel_path)
|
||||
status, out_file = status_and_output(rel_path)
|
||||
cat = categorise(rel_path)
|
||||
|
||||
# Skip files already in DB unless rebuilding
|
||||
if not rebuild:
|
||||
cur.execute("SELECT 1 FROM functions WHERE source_file=? LIMIT 1", (rel_path,))
|
||||
if cur.fetchone():
|
||||
@@ -280,10 +301,10 @@ def build(rebuild=False):
|
||||
continue
|
||||
|
||||
if not labels:
|
||||
# Index the file itself as a single entry so it isn't lost
|
||||
labels = [os.path.splitext(os.path.basename(rel_path))[0]]
|
||||
|
||||
for label in labels:
|
||||
status, out_file = status_and_output(rel_path, label)
|
||||
cur.execute("""
|
||||
INSERT INTO functions (name, source_file, category, status,
|
||||
output_file, created_at, updated_at)
|
||||
|
||||
@@ -7,6 +7,13 @@ All output is JSON so agents can parse it without screen-scraping.
|
||||
** NEVER access translation.db with raw SQL. Use this tool exclusively. **
|
||||
Direct sqlite3 calls bypass the audit log and will use the wrong table name.
|
||||
|
||||
Status pipeline
|
||||
---------------
|
||||
needs_translation → in_progress → translated → [wired → verified]
|
||||
↑
|
||||
(swarm terminal)
|
||||
wired and verified are Claude Code session transitions only.
|
||||
|
||||
Commands
|
||||
--------
|
||||
status Queue summary counts by category × status.
|
||||
@@ -14,26 +21,25 @@ Commands
|
||||
List matching functions.
|
||||
claim --agent ID [--count N] [--category C]
|
||||
Atomically claim N jobs (default 1).
|
||||
Returns claimed rows + suggested scratch paths.
|
||||
manifest --id ID Print the manifest header a worker must write
|
||||
at the top of its scratch file.
|
||||
manifest --id ID Print the manifest header a worker must write.
|
||||
complete --id ID --scratch PATH [--agent ID]
|
||||
Mark a function complete. Parses the worker
|
||||
notes block from the scratch file and stores
|
||||
it in the DB. Verifies the file exists first.
|
||||
Worker marks job translated. Scratch file must
|
||||
exist and be different from the output path.
|
||||
place --id ID --output PATH [--agent ID]
|
||||
Integration Agent records final dos_port/src/
|
||||
placement and deletes the scratch file.
|
||||
reset --id ID Return any function to needs_translation
|
||||
(preserves category). Maintenance command.
|
||||
wire --id ID [--agent ID] Claude Code: translated → wired.
|
||||
verify --id ID [--agent ID] Claude Code: wired → verified.
|
||||
recategorize --id ID --category C [--agent ID] [--notes MSG]
|
||||
Move a job to a different category (e.g.
|
||||
simple→complex when HW I/O is discovered).
|
||||
Move a job to a different category.
|
||||
Resets status to needs_translation. Logged.
|
||||
unclaim --id ID Return a claimed job to needs_translation.
|
||||
fail --id ID [--notes MSG] Return a job with a failure note.
|
||||
log --id ID Full audit log for one function.
|
||||
pending-placement List complete jobs waiting for Integration Agent.
|
||||
translation-log-entry --id ID Emit a formatted docs/translation_log.md entry
|
||||
for Docs_Commit_Agent to append to that file.
|
||||
pending-placement List translated jobs waiting for Integration Agent.
|
||||
translation-log-entry --id ID Emit a formatted docs/translation_log.md entry.
|
||||
|
||||
Exit codes: 0 = success, 1 = error (details in JSON 'error' key).
|
||||
"""
|
||||
@@ -46,12 +52,18 @@ import sqlite3
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO_ROOT = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..'))
|
||||
DB_PATH = os.path.join(SCRIPT_DIR, 'translation.db')
|
||||
SCRATCH_DIR = os.path.join(REPO_ROOT, 'dos_port', 'scratch')
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO_ROOT = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..'))
|
||||
DB_PATH = os.path.join(SCRIPT_DIR, 'translation.db')
|
||||
SCRATCH_DIR = os.path.join(REPO_ROOT, 'dos_port', 'scratch')
|
||||
|
||||
# ── DB helpers ──────────────────────────────────────────────────────────────────
|
||||
ALL_STATUSES = [
|
||||
'needs_translation', 'in_progress', 'translated', 'wired', 'verified', 'skip',
|
||||
# legacy values kept so old rows survive migration
|
||||
'complete', 'unverified',
|
||||
]
|
||||
|
||||
# ── DB helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def db():
|
||||
if not os.path.exists(DB_PATH):
|
||||
@@ -68,6 +80,13 @@ def _migrate(con):
|
||||
con.execute("ALTER TABLE functions ADD COLUMN scratch_file TEXT")
|
||||
if 'translation_notes' not in cols:
|
||||
con.execute("ALTER TABLE functions ADD COLUMN translation_notes TEXT")
|
||||
# Rename legacy statuses. Disable CHECK constraints temporarily because the
|
||||
# old schema only allows 'complete'/'unverified'; new names are 'translated'
|
||||
# and 'needs_translation'.
|
||||
con.execute("PRAGMA ignore_check_constraints = ON")
|
||||
con.execute("UPDATE functions SET status='translated' WHERE status='complete'")
|
||||
con.execute("UPDATE functions SET status='needs_translation' WHERE status='unverified'")
|
||||
con.execute("PRAGMA ignore_check_constraints = OFF")
|
||||
con.commit()
|
||||
|
||||
def scratch_path(fid, label):
|
||||
@@ -94,7 +113,7 @@ def log_transition(cur, function_id, old_status, new_status, agent=None, notes=N
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""", (function_id, old_status, new_status, agent, notes, now_utc()))
|
||||
|
||||
# ── Manifest / notes parsing ────────────────────────────────────────────────────
|
||||
# ── Manifest / notes parsing ─────────────────────────────────────────────────────
|
||||
|
||||
def make_manifest(row):
|
||||
sp = scratch_path(row['id'], row['name'])
|
||||
@@ -124,29 +143,25 @@ def make_manifest(row):
|
||||
]
|
||||
return '\n'.join(lines)
|
||||
|
||||
# Fields the worker fills in — parsed from the scratch file header.
|
||||
_WORKER_FIELDS = ('registers', 'hflag', 'bug_tags', 'notes')
|
||||
|
||||
def parse_notes_from_scratch(path):
|
||||
"""Read the manifest header from a scratch file and return a dict of worker notes."""
|
||||
result = {f: '' for f in _WORKER_FIELDS}
|
||||
try:
|
||||
with open(path, encoding='utf-8', errors='replace') as fh:
|
||||
for line in fh:
|
||||
# Stop at the CODE BELOW sentinel
|
||||
if 'CODE BELOW' in line:
|
||||
break
|
||||
m = re.match(r';\s*(registers|hflag|bug_tags|notes)\s*:\s*(.*)', line)
|
||||
if m:
|
||||
key, val = m.group(1), m.group(2).strip()
|
||||
# Ignore unfilled placeholder values
|
||||
if not (val.startswith('(') and val.endswith(')')):
|
||||
result[key] = val
|
||||
except OSError:
|
||||
pass
|
||||
return result
|
||||
|
||||
# ── sub-commands ────────────────────────────────────────────────────────────────
|
||||
# ── sub-commands ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def cmd_status(_args):
|
||||
con = db()
|
||||
@@ -237,7 +252,7 @@ def cmd_manifest(args):
|
||||
con.close()
|
||||
|
||||
def cmd_complete(args):
|
||||
"""Worker calls this after writing the scratch file."""
|
||||
"""Worker marks job translated; scratch file must exist and differ from output."""
|
||||
con = db()
|
||||
cur = con.cursor()
|
||||
row = cur.execute("SELECT id, status, name FROM functions WHERE id=?",
|
||||
@@ -248,46 +263,57 @@ def cmd_complete(args):
|
||||
if not os.path.exists(full_scratch):
|
||||
err({'error': f'Scratch file not found: {args.scratch}',
|
||||
'hint': 'Worker must write the file before calling complete.'})
|
||||
# Parse worker notes from the manifest header
|
||||
worker_notes = parse_notes_from_scratch(full_scratch)
|
||||
notes_json = json.dumps(worker_notes)
|
||||
old = row['status']
|
||||
cur.execute("""
|
||||
UPDATE functions
|
||||
SET status='complete', scratch_file=?, translation_notes=?,
|
||||
SET status='translated', scratch_file=?, translation_notes=?,
|
||||
assigned_agent=?, updated_at=?
|
||||
WHERE id=?
|
||||
""", (args.scratch, notes_json, args.agent, now_utc(), args.id))
|
||||
log_transition(cur, args.id, old, 'complete', args.agent,
|
||||
log_transition(cur, args.id, old, 'translated', args.agent,
|
||||
f'scratch: {args.scratch}')
|
||||
con.commit()
|
||||
out({'id': args.id, 'name': row['name'], 'old_status': old,
|
||||
'new_status': 'complete', 'scratch_file': args.scratch,
|
||||
'new_status': 'translated', 'scratch_file': args.scratch,
|
||||
'parsed_notes': worker_notes,
|
||||
'next_step': 'Integration Agent: run `place --id` after moving to dos_port/src/'})
|
||||
con.close()
|
||||
|
||||
def cmd_place(args):
|
||||
"""Integration Agent calls this after moving scratch → dos_port/src/."""
|
||||
"""Integration Agent records placement. Guards against scratch == output."""
|
||||
con = db()
|
||||
cur = con.cursor()
|
||||
row = cur.execute("SELECT id, status, name, scratch_file FROM functions WHERE id=?",
|
||||
(args.id,)).fetchone()
|
||||
if not row:
|
||||
err({'error': f'No function with id={args.id}'})
|
||||
if row['status'] != 'complete':
|
||||
err({'error': f'Function {row["name"]} is not complete (status={row["status"]})',
|
||||
'hint': 'Only complete functions can be placed.'})
|
||||
if row['status'] != 'translated':
|
||||
err({'error': f'Function {row["name"]} is not translated (status={row["status"]})',
|
||||
'hint': 'Only translated functions can be placed.'})
|
||||
|
||||
full_output = os.path.join(REPO_ROOT, args.output)
|
||||
if row['scratch_file']:
|
||||
full_scratch = os.path.join(REPO_ROOT, row['scratch_file'])
|
||||
try:
|
||||
if os.path.realpath(full_scratch) == os.path.realpath(full_output):
|
||||
err({'error': 'scratch and output resolve to the same file — '
|
||||
'create a separate scratch file in dos_port/scratch/',
|
||||
'scratch': row['scratch_file'],
|
||||
'output': args.output})
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
cur.execute("""
|
||||
UPDATE functions
|
||||
SET output_file=?, scratch_file=NULL, assigned_agent=?, updated_at=?
|
||||
WHERE id=?
|
||||
""", (args.output, args.agent, now_utc(), args.id))
|
||||
log_transition(cur, args.id, 'complete', 'complete', args.agent,
|
||||
log_transition(cur, args.id, 'translated', 'translated', args.agent,
|
||||
f'placed → {args.output}')
|
||||
con.commit()
|
||||
|
||||
# Delete the scratch file now that it has been placed in dos_port/src/.
|
||||
deleted_scratch = None
|
||||
if row['scratch_file']:
|
||||
full_scratch = os.path.join(REPO_ROOT, row['scratch_file'])
|
||||
@@ -295,26 +321,89 @@ def cmd_place(args):
|
||||
os.remove(full_scratch)
|
||||
deleted_scratch = row['scratch_file']
|
||||
except OSError:
|
||||
pass # already gone — not an error
|
||||
pass
|
||||
|
||||
out({'id': args.id, 'name': row['name'],
|
||||
'scratch_deleted': deleted_scratch,
|
||||
'output_file': args.output})
|
||||
con.close()
|
||||
|
||||
def cmd_reset(args):
|
||||
"""Return any function to needs_translation, preserving category."""
|
||||
con = db()
|
||||
cur = con.cursor()
|
||||
row = cur.execute("SELECT id, status, name, assigned_agent FROM functions WHERE id=?",
|
||||
(args.id,)).fetchone()
|
||||
if not row:
|
||||
err({'error': f'No function with id={args.id}'})
|
||||
old = row['status']
|
||||
cur.execute("""
|
||||
UPDATE functions
|
||||
SET status='needs_translation', assigned_agent=NULL,
|
||||
scratch_file=NULL, updated_at=?
|
||||
WHERE id=?
|
||||
""", (now_utc(), args.id))
|
||||
log_transition(cur, args.id, old, 'needs_translation',
|
||||
row['assigned_agent'], 'reset by maintenance')
|
||||
con.commit()
|
||||
out({'id': args.id, 'name': row['name'],
|
||||
'old_status': old, 'new_status': 'needs_translation'})
|
||||
con.close()
|
||||
|
||||
def cmd_wire(args):
|
||||
"""Claude Code session: translated → wired."""
|
||||
con = db()
|
||||
cur = con.cursor()
|
||||
row = cur.execute("SELECT id, status, name FROM functions WHERE id=?",
|
||||
(args.id,)).fetchone()
|
||||
if not row:
|
||||
err({'error': f'No function with id={args.id}'})
|
||||
if row['status'] != 'translated':
|
||||
err({'error': f'{row["name"]} is not translated (status={row["status"]})',
|
||||
'hint': 'Only translated functions can be wired.'})
|
||||
cur.execute("""
|
||||
UPDATE functions SET status='wired', assigned_agent=?, updated_at=?
|
||||
WHERE id=?
|
||||
""", (args.agent, now_utc(), args.id))
|
||||
log_transition(cur, args.id, 'translated', 'wired', args.agent)
|
||||
con.commit()
|
||||
out({'id': args.id, 'name': row['name'],
|
||||
'old_status': 'translated', 'new_status': 'wired'})
|
||||
con.close()
|
||||
|
||||
def cmd_verify(args):
|
||||
"""Claude Code session: wired → verified (confirmed working in DOSBox-X)."""
|
||||
con = db()
|
||||
cur = con.cursor()
|
||||
row = cur.execute("SELECT id, status, name FROM functions WHERE id=?",
|
||||
(args.id,)).fetchone()
|
||||
if not row:
|
||||
err({'error': f'No function with id={args.id}'})
|
||||
if row['status'] != 'wired':
|
||||
err({'error': f'{row["name"]} is not wired (status={row["status"]})',
|
||||
'hint': 'Only wired functions can be verified.'})
|
||||
cur.execute("""
|
||||
UPDATE functions SET status='verified', assigned_agent=?, updated_at=?
|
||||
WHERE id=?
|
||||
""", (args.agent, now_utc(), args.id))
|
||||
log_transition(cur, args.id, 'wired', 'verified', args.agent)
|
||||
con.commit()
|
||||
out({'id': args.id, 'name': row['name'],
|
||||
'old_status': 'wired', 'new_status': 'verified'})
|
||||
con.close()
|
||||
|
||||
def cmd_pending_placement(_args):
|
||||
con = db()
|
||||
rows = con.execute("""
|
||||
SELECT id, name, source_file, category, scratch_file, assigned_agent
|
||||
FROM functions
|
||||
WHERE status = 'complete' AND output_file IS NULL
|
||||
WHERE status = 'translated' AND output_file IS NULL
|
||||
ORDER BY source_file, name
|
||||
""").fetchall()
|
||||
out({'count': len(rows), 'pending': [dict(r) for r in rows]})
|
||||
con.close()
|
||||
|
||||
def cmd_translation_log_entry(args):
|
||||
"""Emit a formatted docs/translation_log.md entry ready to append."""
|
||||
con = db()
|
||||
row = con.execute("SELECT * FROM functions WHERE id=?", (args.id,)).fetchone()
|
||||
if not row:
|
||||
@@ -328,12 +417,12 @@ def cmd_translation_log_entry(args):
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
notes = {}
|
||||
|
||||
source = fn.get('source_file', '(unknown)')
|
||||
output = fn.get('output_file') or fn.get('scratch_file') or '(not yet placed)'
|
||||
registers = notes.get('registers') or '(not recorded)'
|
||||
hflag = notes.get('hflag') or '(not recorded)'
|
||||
bug_tags = notes.get('bug_tags') or 'none'
|
||||
freenotes = notes.get('notes') or '(none)'
|
||||
source = fn.get('source_file', '(unknown)')
|
||||
output = fn.get('output_file') or fn.get('scratch_file') or '(not yet placed)'
|
||||
registers = notes.get('registers') or '(not recorded)'
|
||||
hflag = notes.get('hflag') or '(not recorded)'
|
||||
bug_tags = notes.get('bug_tags') or 'none'
|
||||
freenotes = notes.get('notes') or '(none)'
|
||||
|
||||
entry = f"""
|
||||
## {fn['name']}
|
||||
@@ -353,7 +442,6 @@ def cmd_translation_log_entry(args):
|
||||
con.close()
|
||||
|
||||
def cmd_recategorize(args):
|
||||
"""Move a job to a different category and reset it to needs_translation."""
|
||||
con = db()
|
||||
cur = con.cursor()
|
||||
row = cur.execute(
|
||||
@@ -364,7 +452,7 @@ def cmd_recategorize(args):
|
||||
err({'error': f'No function with id={args.id}'})
|
||||
if row['category'] == args.category:
|
||||
err({'error': f'{row["name"]} is already category={args.category}'})
|
||||
old_cat = row['category']
|
||||
old_cat = row['category']
|
||||
old_status = row['status']
|
||||
note = args.notes or f'recategorized {old_cat}→{args.category}'
|
||||
cur.execute("""
|
||||
@@ -441,7 +529,7 @@ def cmd_log(args):
|
||||
out({'function': fn_dict, 'log': [dict(r) for r in log]})
|
||||
con.close()
|
||||
|
||||
# ── CLI wiring ──────────────────────────────────────────────────────────────────
|
||||
# ── CLI wiring ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
@@ -452,8 +540,7 @@ def main():
|
||||
|
||||
p = sub.add_parser('list', help='List functions')
|
||||
p.add_argument('--category', choices=['simple', 'complex'])
|
||||
p.add_argument('--status',
|
||||
choices=['needs_translation','in_progress','unverified','complete','skip'])
|
||||
p.add_argument('--status', choices=ALL_STATUSES)
|
||||
p.add_argument('--limit', type=int)
|
||||
|
||||
p = sub.add_parser('claim', help='Claim jobs for an agent')
|
||||
@@ -465,10 +552,10 @@ def main():
|
||||
p.add_argument('--id', type=int, required=True)
|
||||
|
||||
p = sub.add_parser('complete',
|
||||
help='Worker marks job done; parses notes from scratch file header')
|
||||
help='Worker marks job translated; parses notes from scratch file')
|
||||
p.add_argument('--id', type=int, required=True)
|
||||
p.add_argument('--scratch', required=True,
|
||||
help='Repo-relative path to the scratch file (dos_port/scratch/…)')
|
||||
help='Repo-relative path to scratch file in dos_port/scratch/')
|
||||
p.add_argument('--agent')
|
||||
|
||||
p = sub.add_parser('place',
|
||||
@@ -478,6 +565,20 @@ def main():
|
||||
help='Repo-relative path of the placed file in dos_port/src/')
|
||||
p.add_argument('--agent')
|
||||
|
||||
p = sub.add_parser('reset',
|
||||
help='Return any function to needs_translation (preserves category)')
|
||||
p.add_argument('--id', type=int, required=True)
|
||||
|
||||
p = sub.add_parser('wire',
|
||||
help='Claude Code: mark function as wired into live game loop')
|
||||
p.add_argument('--id', type=int, required=True)
|
||||
p.add_argument('--agent')
|
||||
|
||||
p = sub.add_parser('verify',
|
||||
help='Claude Code: mark function as verified working in DOSBox-X')
|
||||
p.add_argument('--id', type=int, required=True)
|
||||
p.add_argument('--agent')
|
||||
|
||||
p = sub.add_parser('recategorize',
|
||||
help='Move a job to a different category (resets to needs_translation)')
|
||||
p.add_argument('--id', type=int, required=True)
|
||||
@@ -486,13 +587,13 @@ def main():
|
||||
p.add_argument('--notes')
|
||||
|
||||
sub.add_parser('pending-placement',
|
||||
help='List complete jobs waiting for Integration Agent')
|
||||
help='List translated jobs waiting for Integration Agent')
|
||||
|
||||
p = sub.add_parser('translation-log-entry',
|
||||
help='Emit a formatted translation_log.md entry for Docs_Commit_Agent')
|
||||
p.add_argument('--id', type=int, required=True)
|
||||
|
||||
p = sub.add_parser('unclaim', help='Return a job to the queue')
|
||||
p = sub.add_parser('unclaim', help='Return a claimed job to needs_translation')
|
||||
p.add_argument('--id', type=int, required=True)
|
||||
|
||||
p = sub.add_parser('fail', help='Return a job with a failure note')
|
||||
@@ -504,18 +605,21 @@ def main():
|
||||
|
||||
args = ap.parse_args()
|
||||
{
|
||||
'status': cmd_status,
|
||||
'list': cmd_list,
|
||||
'claim': cmd_claim,
|
||||
'manifest': cmd_manifest,
|
||||
'complete': cmd_complete,
|
||||
'place': cmd_place,
|
||||
'pending-placement': cmd_pending_placement,
|
||||
'translation-log-entry': cmd_translation_log_entry,
|
||||
'recategorize': cmd_recategorize,
|
||||
'unclaim': cmd_unclaim,
|
||||
'fail': cmd_fail,
|
||||
'log': cmd_log,
|
||||
'status': cmd_status,
|
||||
'list': cmd_list,
|
||||
'claim': cmd_claim,
|
||||
'manifest': cmd_manifest,
|
||||
'complete': cmd_complete,
|
||||
'place': cmd_place,
|
||||
'reset': cmd_reset,
|
||||
'wire': cmd_wire,
|
||||
'verify': cmd_verify,
|
||||
'pending-placement': cmd_pending_placement,
|
||||
'translation-log-entry': cmd_translation_log_entry,
|
||||
'recategorize': cmd_recategorize,
|
||||
'unclaim': cmd_unclaim,
|
||||
'fail': cmd_fail,
|
||||
'log': cmd_log,
|
||||
}[args.cmd](args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user