9 KiB
AGI port — session handoff
This document captures the state of the AGI interpreter port as of 2026-05-15. Hand it to a fresh Claude Code session to pick up where the previous one left off.
Project context
JoeyLib is a unified C game-dev library targeting Apple IIgs (perf floor / reference), Amiga, Atari ST, MS-DOS. The AGI port is a from-scratch reimplementation of Sierra's Adventure Game Interpreter v2 on top of JoeyLib's surface / draw / input APIs. Written from public AGI format specs only -- NOT derived from ScummVM / NAGI / Sarien (those are GPL'd; JoeyLib is not).
Test data: KQ3 v2, locally installed at
examples/agi/gamedata/kq3/. The user owns the game and is not
distributing it; gamedata is gitignored.
Current phase
Phase 1 sub-D (callback-driven rendering): verified working. Logic.0 + room 45 init paint KQ3's title screen on every supported port. Ego sprite (VIEW 0, Graham) draws on top with priority masking, WASD/arrow keys move him, ESC quits.
What's next: Phase 1 sub-E (input + animation opcodes). The
title screen does not auto-advance because the VM still stubs ~150
opcodes including wait, set.menu, accept.input, animation
timers, etc. Real KQ3 progression past the title requires implementing
enough of those to let logic.45 complete its intro and call new.room.
Phase 2 sub-A onward: parser, NPC actors, sound (NTP / PSG).
What works (verified)
- Resource loader (LOGDIR / PICDIR / VIEWDIR / SNDDIR / VOL.x)
- PIC decoder (visual + priority planes, flood fill, vector draw)
- VIEW decoder (cel parse + RLE blit + priority masking + mirror)
- LOGIC VM: 256 vars, 256 flags, 8-deep call stack
- Real handlers: arithmetic (0x00-0x11), OP_IF (0xFF), OP_GOTO (0xFE), OP_NEW_ROOM (0x12/0x13), OP_LOAD_LOGIC (0x14/0x15), OP_CALL (0x16/0x17), OP_LOAD_PIC (0x18), OP_DRAW_PIC (0x19), OP_SHOW_PIC (0x1A), OP_DISCARD_PIC (0x1B), OP_OVERLAY_PIC (0x1C)
- All ~180 other action opcodes are stubs (skip their arg bytes
via
kActionArgBytes[256]table)
- Host callbacks: fetchLogic / loadPic / drawPic / showPic / discardPic / overlayPic
- Per-platform save-under for the ego sprite (no full-stage memcpy per frame; IIgs uses MVN-based codegen)
- Cross-platform binary works on all 4 ports (
AGI.EXE,AGI.PRG,Agi, IIgsAGI)
What does NOT work
- Title screen never advances. Stubs the input / timer opcodes the title uses. Workaround: starting-room override (argv[2]).
- No NPC actors. animate.obj / set.cel / set.loop are stubs.
- No parser. said() and the word-tokenize opcodes are stubs.
- No sound. Phase 3 work.
- IIgs run path for AGI is not wired.
scripts/run-agi.sh iigsprints an explicit "not yet wired" message describing the missing disk-packager work (need bigger 2mg volume + case-folded ProDOS names + game-data argument topackage-disk.sh). - ST / Amiga can't pass argv. Hatari
--autoand Amiga startup-sequence don't forward args, so the starting-room override only works on DOS. ST / Amiga AGI starts at the title screen. Fix needs either a config-file stage (ROOM.TXTread at startup) or enough title opcodes for the title to advance naturally.
How to test
source toolchains/env.sh
# Host-side parsers + VM (fast iteration, no emulator)
./scripts/test-agi.sh
# Cross-platform binary in DOSBox
./scripts/run-agi.sh dos # title screen
./scripts/run-agi.sh dos kq3 1 # jump to room 1
./scripts/run-agi.sh dos kq3 3 # jump to room 3
# Other platforms (no argv support yet, always title)
./scripts/run-agi.sh atarist
./scripts/run-agi.sh amiga
./scripts/run-agi.sh iigs # prints "not wired" message
Files touched (this session)
New:
src/core/random.c— xorshift32 PRNG (jlRandom / jlRandomRange / jlRandomSeed)examples/agi/*.c— agi.c, agiRes.c, agiPic.c, agiView.c, agiVm.cexamples/agi/agi.h— all AGI types + public APItests/agi/*.c— testAgiRes / testAgiPic / testAgiView / testAgiVm / testAgiPipelinescripts/test-agi.sh— host-build runner for all five testsscripts/run-agi.sh— cross-platform emulator launcher (dos | amiga | atarist | iigs)
Modified:
include/joey/core.h— random prototypesinclude/joey/platform.h— auto-detect from compiler defines.gitignore— examples/agi/gamedata/- All four make/*.mk — added agi to EXAMPLES list
Architectural decisions worth remembering
Render loop: save-under, not full stage copy
The naive per-frame jlSurfaceCopy(stage, gBackdrop) is 32 KB of
memcpy + present overhead -- not acceptable on a 2.8 MHz IIgs. The
current code:
cbShowPicpaints the picture onto the stage ONCE and snapshots it asgBackdrop. SetsgPicReady = trueand invalidates any prior ego save-under.- Per frame:
jlSpriteRestoreUnderthe last-frame ego rect (puts clean backdrop bytes back),jlSpriteSaveUnderthe new ego rect, thenagiViewDrawpaints the cel with priority masking. - Placeholder sprite for save-under is 4x5 tiles = 32x40 stage
pixels, sized to cover any AGI v2 ego cel. Compiled via
jlSpriteCompileso save/restore use the platform's compiled fast path (MVN block-copy on IIgs, etc.) instead of the C interpreter fallback.
Per-frame memory traffic: ~1.3 KB save+restore vs ~32 KB full copy.
VM throttling
AGI's native cycle is ~6 Hz. Running logic.0 every frame (60 Hz) is
6800-byte switch dispatcher × 60 = ~400k opcodes/sec for nothing.
agi.c now runs runVmCycle once every 10 frames (~6 Hz on 60 Hz
refresh, ~5 Hz on 50 Hz PAL). Input polling, ego movement, render
still run at full refresh -- only the bytecode interp is throttled.
Startup pumps the VM until gPicReady (or 32 cycles) so the user
never sees a flash of COLOR_MISSING before the first room paints.
VIEW mirror handling
Sierra's encoder writes mirror=1, src=N on BOTH loops in a mirror
pair, where N is the lower-numbered loop. agiView.c:resolveMirror
must only treat a cel as mirrored when src != current_loop. KQ3
view 0 hit this directly: both loop 0 (right) and loop 1 (left) had
mirror=1, src=0, and our original code flipped both -- so pressing
left and right both faced left. Fixed.
Starting-room override
agi.c argv[2] = starting room number 1..255. When set,
runVmCycle's NEW_ROOM handler substitutes the override for
vm.newRoomId on the FIRST NEW_ROOM halt only (one-shot). Lets the
user skip past KQ3's title room (45) into the actual game. Once the
override fires, subsequent transitions go through unchanged.
scripts/run-agi.sh accepts <platform> [game] [room] and forwards
the room to DOS via dosbox -c "AGI.EXE . <room>". ST and Amiga
can't pass argv via autostart so their AGI starts at the title.
Default game dir is "." not "agidemo"
resolveGameDir defaults to "." (CWD). On platforms whose autostart
can't pass argv (Hatari, AmigaDOS), run-agi.sh stages the AGI
binary plus the v2 game data files (LOGDIR / PICDIR / etc.) flat in
a temp directory and points the emulator at it; AGI runs from CWD =
that temp dir and finds the data files relative to "./".
Likely next-step work
The user's natural next ask is "implement enough title opcodes to let KQ3's title screen auto-advance." That probably means:
wait(cycle.time gate)input.line/accept.input- Animation timers:
cycle.time,current.loop, etc. set.menu/disable.item(KQ3 title shows a menu)
Alternative path: skip the title work and implement enough core opcodes to make a gameplay room functional. animate.obj, set.view.v, set.cel, set.loop, position, draw, erase. That gets us walking around KQ3 room 1 (Manannan's house) with Graham instead of a static title.
The user should pick which to prioritize.
Memory entries you should know about
The repo's auto-memory at
~/.claude/projects/-home-scott-claude-joeylib/memory/ includes
JoeyLib-wide context (CMake quirks, ORCA-C gotchas, perf
constraints) plus project_joeylib_agi_plan.md which describes the
overall AGI port plan. All persist into the next session
automatically; no need to reload.
Specifically relevant to the AGI port:
project_joeylib_agi_plan.md— overall plan, 3-4 month estimatefeedback_orca_c_atoi_segment.md— don't use atoi in AGI code (it pushes the IIgs ORCA-C stdio cluster past 64 KB; we use a manual parseArgU8 instead)project_perf_directive.md— IIgs is the perf floor; every other target must match or beat itfeedback_iigs_speed.md— IIgs must be absolute fastest technically possible; the save-under decision was made under this constraint.
Git / user preferences
- The user manages git themselves -- do NOT commit / push / branch
on JoeyLib without explicit ask (see memory
feedback_git.md). - Always
source toolchains/env.shbefore anymake-- the user has added that to .claude/settings.local.json allow list. - DJGPP / ORCA / vasm / m68k-atari-mint paths come from env.sh.
- The user does NOT need hand-holding -- they're an expert C dev. Skip excessive explanation; describe what was done and move on.