11 KiB
AGENTS.md
Virtual Jaguar libretro core — Atari Jaguar emulator on the libretro API. C, GPLv3. Upstream:
http://shamusworld.gotdns.org/git/virtualjaguar.
Shorthand, LLM-oriented. Load-bearing rules are inline below; depth lives in docs/agent/*.md
and the linked docs — read the relevant one before working in that area (they load on demand,
so they cost nothing until you open them).
Reference index
docs/agent/build.md— build commands, C89 exempt list, test ABI /TEST_EXPORTSrelink, build-identity guard, stale-.o hazard.docs/agent/hardware.md— 4-processor model, memory map, clocks, source layout, distilled JTRM index (docs/jtrm-*.md), known limitations.docs/jtrm-errata.md— JTRM errata; Atari bug lists vs what we model.docs/agent/testing.md— shared harness, full harness catalog, vjtrace, audio-test requirements, crash watchdog signatures, acid gating, profiling, headless caveat.docs/agent/github.md— PR↔issue linking, Copilot/Kimi reviews, release process, site publishing, roadmap.
Always-on rules
- Branch off
libretro/develop(GitFlow integration branch; local develop may be stale).masteris release-only. PRs →developunlesshotfix/*/release/*. Detail:docs/agent/github.md. - C89 / GNU89, strict. No mid-block declarations (all vars at top of block, before any
statement — most common violation); no C99 (
for(int i…), compound literals, designated initializers, VLAs). Runbash scripts/c89-lint.sh src/YOURFILE.cbefore pushing. Exempt files + detail:docs/agent/build.md. - Host builds: prefix
DEVELOPER_DIR=/Library/Developer/CommandLineToolsor macOS raises an App Management prompt on everymake/cc. Don'txcode-select --switchglobally (iOS cross-builds need Xcode). - Hardware accuracy: verify clocks/registers against the JTRM PDFs directly, never source
comments (comments have been wrong, e.g. PIT clock) and never assume a
docs/jtrm-*.mdline is JTRM-verified just because it lives in that file — per-line provenance is a mix: check whether the section's ownSource:/Derived from:tag cites a manual page or just a source file (issue #522). ADerived from: ... NOT verified against the JTRMtag is not sufficient grounds for a hardware-accuracy decision; read the cited PDF page yourself. Refs:docs/jtrm-*.md,docs/atari-jaguar-1999/. - Audio/DSP changes (
src/jerry/dac.c,dsp.c, HLE audio path insrc/core/jaguar.c, DSP IRQ return logic) MUST clear BOTHtest_audio_clippingANDtest_audio_presence— clipping alone misses the silencing-regression class (PR #170 lesson). Then verify in RetroArch. Detail:docs/agent/testing.md. - Every PR must be linked to its issue in the GitHub Development panel — put a link tag in the
PR body and CI makes the link.
Closes #Ndoes NOT link by itself on this repo (default branch ismaster, PRs targetdevelop, so GitHub ignores the keyword), butpr-issue-link.ymlscans the body forCloses/Fixes/Resolves/Refs #Nor<!-- link-issue: #N -->and creates the link viaaddCloseIssueReferences. Same thing by hand:scripts/pr-link-issue.sh <pr> [issue]. Unlinked and untagged PRs still fail CI; use theno-issuelabel to opt out. Detail:docs/agent/github.md.
Data-safety rules (irreplaceable / can hang forever)
- Private ROMs.
test/roms/privateis a symlink to../jaguar-roms-private(outside every checkout, gitignored). Concurrent sessions once destroyed the tree.- Never
git clean -xfd(or any recursive delete) at repo root — it targets exactly the gitignored paths holding irreplaceable data. - Fresh worktree:
ln -sfn "${JAGUAR_ROMS_PRIVATE:?}" test/roms/private(the-nmatters — without it a secondln -sflinks inside the tree). - Cleanup may remove the symlink (
rm -f test/roms/private), never its target. finddoesn't follow symlinks — usefind -L test/roms/private. iCloud restores nest one level deeper (<Title>/<Title>/*.cue) — discover withfind -L, not a fixed depth.
- Never
- Shell aliases
rm/cp/mvare-iin this user's shell; a prompt with no TTY blocks forever (a hungrm -ionce sat for 9 hours;cp a bsilently does nothing and measurements then lie).- Delete with
trash(/usr/bin/trash; recoverable, non-interactive). - If it must go:
command rm -f/command cp -f/command mv -f. Never barerm/cp/mvin a chain. - Verify a background task actually exited — don't infer from side effects. Check task output;
ps -eo pid,etime,command | grep ' -i 'if something feels slow.
- Delete with
Hardware model (one-paragraph)
Four processors (68000 main @13.3 MHz src/m68000/; GPU RISC @26.6 MHz src/tom/gpu.c; DSP same
ISA src/jerry/dsp.c; Object Processor src/tom/op.c), unified big-endian memory map. TOM
(src/tom/tom.c) = video/GPU/OP/blitter; JERRY (src/jerry/jerry.c) = audio/DSP/timers/EEPROM.
RAM 0x000000 (2 MB), cart 0x800000, TOM regs 0xF00000, JERRY regs 0xF10000.
GET/SET16/32 byte-swap on LE hosts. Frame loop event-driven (JaguarExecuteNew() in
src/core/jaguar.c). System clock 26.590906 MHz NTSC / 26.593900 MHz PAL; 68K = half. Full map +
layout: docs/agent/hardware.md.
libretro.c implements the API (video XRGB8888 dynamic res, audio 48 kHz 16-bit stereo, options
in libretro_core_options.h).
Build & test (quick)
make -j$(getconf _NPROCESSORS_ONLN) # build
make -j$(getconf _NPROCESSORS_ONLN) DEBUG=1 # debug -O0 -g
make TEST_EXPORTS=1 test # wide-ABI test suite (auto-relinks; see build.md)
Runtime crash watchdog (src/core/crash_detect.c, option virtualjaguar_crash_detect) logs
gpu_pc_escape/dsp_pc_escape/gpu_wedge/dsp_wedge/video_stall/cd_seek_wedge/inframe_hang at
the moment of crash — the RetroArch log line points at the broken subsystem. Signatures + all
harnesses: docs/agent/testing.md.
Codex workflow
- Codex reads this file automatically. Keep shared repository rules here;
CLAUDE.mdis a compatibility entry point. Read the relevantdocs/agent/guide before area-specific work. - Use
.agents/skills/for repository skills; Claude-specific hooks and permissions in.claude/do not run in Codex. Apply the data-safety rules above explicitly. - Avoid dumping ROMs, disc images, compiled libraries, objects, or savestates into context.
Inspect source and logs first; use bounded
xxd,nm, orotooloutput when needed. - Claude shell hooks (including RTK rewriting) do not run here. If using RTK, invoke it explicitly and keep raw failure logs available for diagnosis.
- Existing worktrees may contain active work. Inspect
git statusandgit worktree list, and reuse the matching PR checkout when clean; preserve unrelated changes and local config. - For a failed PR check, read the failing job log first. If evidence supports a transient
failure, rerun only that job (
gh run rerun RUN_ID --job JOB_ID) and preserve the passing checks. Reruns use the original commit; a source fix needs fresh validation of the new head. Never mark a failing check successful or bypass a known defect to avoid CI costs. - Do not request paid Copilot/Claude reviews unless explicitly authorized. Review locally
using
.github/skills/code-review/SKILL.mdand report concrete defects with evidence. - Delegate only when requested or required by applicable instructions. Use the least expensive available model suitable for the task; Claude model names are not Codex model identifiers.
Sub-agent guidelines
When spawning agents for work in this repo, brief them with:
- C89 strict — no mid-block declarations, no C99, all vars at top of block; run
bash scripts/c89-lint.shbefore declaring done. - Branch off
libretro/develop(worktree or branch); never targetmaster. - Hardware/emulation work: read
docs/jtrm-*.mdfirst; do NOT trust source comments for clocks/register behavior. - Test after changes —
make -j…to build,make TEST_EXPORTS=1 testfor the suite. Audio/ DSP/HLE changes: BOTHtest_audio_clippingandtest_audio_presence(one alone masks the silencing regression). Blitter:test/tools/test_blitter_compare. Verify in RetroArch before "done" — headless can't tell music from structured noise, nor catch BIOS-mode crashes. - Surgical changes only — no refactors, abstractions, or unrelated cleanup.
- Conventional commits —
fix(component):,perf(component):,test(component):,docs:.
Linux cloud environments
On a Linux (Ubuntu 24.04) cloud VM, the macOS-only notes above
(interactive rm/cp/mv aliases, DEVELOPER_DIR/Xcode) do not apply.
Plain rm/cp/mv are non-interactive and make/cc resolve to the
system GCC/Clang.
Toolchain / dependencies
- No package-manager dependencies to install:
libretro-common/is vendored and the core links only libc (-ldl/-lmfor test harnesses). Requirements are just the system C/C++ toolchain (gcc/g++/make) pluspython3andgit, all preinstalled and ensured by the startup update script. The core build needs no SDL2, OpenGL, or pkg-config.
Build / lint / test (see this guide for the full matrix)
- Build:
make -j$(getconf _NPROCESSORS_ONLN)→virtualjaguar_libretro.so. - Lint:
make lint(C89/MSVC gate). The NEON header check is skipped on this host (clangcan't target NEON here) — CI covers it; that SKIP is expected. - Test:
make test— full white-box suite. Non-obvious: it silently rebuilds the whole tree withTEST_EXPORTS=1(wide ABI +-DVJ_TRACE), and flipping back to a plainmaketriggers another full rebuild (~2 min each). See the "Test ABI and re-linking" section of this guide. make testreports ~15 skipped checks here (Iron Soldier / Atari Karts / Skyhammer / Doom / CD discs, etc.). These are not failures — they need the private ROM corpus, which is absent in the cloud VM (test/roms/privateis an external symlink that does not exist here). A clean run is0 failed.
Running the emulator (there is no GUI app)
This is a libretro core, not a standalone app. To run it headless:
./test/regression_test.sh ./virtualjaguar_libretro.soclones+buildsminiretrofrom GitHub (needs network) and runs the two committed public ROMstest/roms/yarc.j64andtest/roms/jagniccc.j64. Baselines intest/baselines/are committed and should match ("PASS ... identical"); it also exercises determinism, frameskip, savestate and rewind.- Set
MINIRETRO_BIN=/path/to/miniretro-binto skip the clone/build. imagemagickis not installed; regression comparison falls back tocmpand dumped screenshots are plain PNG (viewable directly), so the diff-image path is the only thing lost.- A direct one-ROM run for a quick screenshot:
miniretro --core ./virtualjaguar_libretro.so --rom test/roms/yarc.j64 --output OUT --system OUT --frames 600 --dump-frames-every 599 --no-alarm