joeylib2/AGI-SESSION-HANDOFF.md

9 KiB
Raw Blame History

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, IIgs AGI)

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 iigs prints an explicit "not yet wired" message describing the missing disk-packager work (need bigger 2mg volume + case-folded ProDOS names + game-data argument to package-disk.sh).
  • ST / Amiga can't pass argv. Hatari --auto and 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.TXT read 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.c
  • examples/agi/agi.h — all AGI types + public API
  • tests/agi/*.c — testAgiRes / testAgiPic / testAgiView / testAgiVm / testAgiPipeline
  • scripts/test-agi.sh — host-build runner for all five tests
  • scripts/run-agi.sh — cross-platform emulator launcher (dos | amiga | atarist | iigs)

Modified:

  • include/joey/core.h — random prototypes
  • include/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:

  1. cbShowPic paints the picture onto the stage ONCE and snapshots it as gBackdrop. Sets gPicReady = true and invalidates any prior ego save-under.
  2. Per frame: jlSpriteRestoreUnder the last-frame ego rect (puts clean backdrop bytes back), jlSpriteSaveUnder the new ego rect, then agiViewDraw paints the cel with priority masking.
  3. Placeholder sprite for save-under is 4x5 tiles = 32x40 stage pixels, sized to cover any AGI v2 ego cel. Compiled via jlSpriteCompile so 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 estimate
  • feedback_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 it
  • feedback_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.sh before any make -- 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.