Skip to content

GameFrame 1.1.0

Choose a tag to compare

@tanepiper tanepiper released this 24 Oct 09:28
· 188 commits to main since this release

GameFrame 1.1.0 brings the lessons of POD, Hardwar and Tyrian into the framework: a know-how
layer of failure modes and context with a hook for every agent, tools for 32-bit and 1990s games
(a 32-bit emulator, Watcom register arguments, export gaps, card-library probes, recordings and
replays), tools that spend a plan's monthly API credits, guides for MMX, 3D cards, versions and
the story, a website for each project, and one GameFrame checkout per machine.

Added

  • tools/llm/, tools that spend a Max or Team plan's monthly API credits
    (API credits): name_functions.py suggests names for every
    unnamed function in one Message Batch; skillcheck.py gives an agent one skill and a task
    on the sample game and checks the answer against ground truth (tests/skill-tasks.json);
    headless.py runs an agent unattended under a budget, and docs-check runs re-checker
    and re-editor over the documents; ask.py answers a question from the know-how entries.
    fake_api.py stands in for the API in tests/run.sh. The SDK is optional, in
    requirements-llm.txt. Know-how: api-credits-refused, the messages a refused run gives
    and the fix for each.

  • ghidra-run.sh ... callargs regs=watcom: the constant each register gets before every call,
    in Watcom's order (EAX, EDX, EBX, ECX) or any list, where the decompiler shows no arguments.

  • ghidra-run.sh ... fill-gaps: makes a function at each start exportcov finds, goes on past
    a bad one, and exports the binary again. The sample DLL gains a routine Ghidra misses.

  • tools/verify/replay.py: checks a port's trace against a recording read from outside, where
    one sample can mix frames; a value passes when any state in its window holds it.

  • tools/triage/axes.py: the up axis, its sign and the handedness of a 3D game, from a recorded
    path over the level's vertices.

  • tools/pe/probegen.py: a probe library for a DLL a 32-bit game imports, exporting the same
    names (decorated __stdcall ones too) and logging every call with its arguments.

  • The gameframe-scale crate: nearest, Scale2x (EPX) and a CRT look, for a finished frame.

  • re-browse section 5: growing the viewer into an editor.

  • tools/triage/buildid.py: everything a copy says about its own version (the disc's mastering
    date, link dates, VERSIONINFO, version strings, readme and setup lines), written into the
    project's docs/versions.md. re-inventory, re-start and the versions guide check on day one
    whether the copy is the latest official build, and where to search for later official
    releases; the template has a "Build identity" and an "Is this the latest official version?"
    section.

  • Versions and their deltas: the versions guide now starts with
    finding every edition, takes the last version released as the default reference, and has the
    steps for moving to a newer edition mid-project. A project template, docs/versions.md,
    records each edition, its checksums, languages, content and builds, the data and code
    deltas, the extras and the reference. re-inventory and re-start have the step.

  • tools/triage/versiondiff.py: two editions compared as folders or disc images: files added,
    removed and changed by folder, PE link dates and languages, as Markdown.

  • Know-how: context-newer-edition-mid-project, from POD's first edition and POD Gold.

  • ExportAll.java writes bodies.tsv, every piece of every function (start, end, entry), and
    exportcov.py reads it beside functions.tsv (or with --bodies). Before, a function whose
    body lies in several pieces counted as one run from its entry: Hardwar's export measured
    93.7% where 98.0% of its code is in functions.

  • Know-how: a project whose ./gameframe runs a second clone or another project's copy of
    GameFrame (pin the machine's one checkout in .env), and scripted edits that stop matching
    after a formatter reflows the code.

  • Three guides from the POD: Planet of Death project:
    Recording the original, and replaying it,
    Replacement libraries for a 1990s game and
    Writers, editors and mods; and a section on the emulated-PC route in
    Running the original.

  • scripts/zcc.sh: rustc's linker for 32-bit Windows DLLs through zig cc, exporting exactly
    the names in a .def file.

  • pe32_emu: array, put and get lay out the game's structures; the sample DLL gains
    sum_list and heaviest_path to test them.

  • tools/formats/derived.py: which fields next to geometry are bounds, centres, normals or
    plane distances.

  • Reading the story, from the Hardwar project: the guide
    Reading the story for design decisions, which makes the case that a
    game's manual and its own text say what each system is for and which must be exact, with
    Hardwar as the worked example. re-inventory has a new step, "Read the story", and
    re-start, re-surveyor and the games taxonomy point to it. New projects get a
    docs/lore.md template with the table "in the story → in the code → evidence".

  • A case study of Hardwar (1998), and Hardwar in the vehicle simulations' reference games.

  • tools/triage/sentences.py: the sentences in a program or a Ghidra strings.txt, ASCII and
    UTF-16, with addresses, for reading the story.

  • tools/formats/shadetable.py: a palette game's shade, fog or light table, with the levels
    that are black, unchanged and white, the colour it fades toward, the shift that reaches
    normal light, and a picture.

  • xorscan.py --records and --key-in: a key XORed into one field of every directory record,
    read from the names' zero padding and completed from the executable.

  • rawimage.py and catalog.py --dac shift: 6-bit palettes shifted left by 2 (63 is 252).
    The catalogue fills a palette of fewer than 256 colours with magenta.

  • constants.py reports Park-Miller's 16807 without --all when its modulus 0x7fffffff, or
    Schrage's 127773 or 2836, is beside it.

  • new-site.sh --manual: a Pages workflow that runs only when started by hand. New sites get a
    theme to colour from the game and a public/ folder; re-site asks for figures and diagrams
    in every diary entry from the first. re-browse describes the viewer's --export mode for
    agents.

  • Know-how from Hardwar: Watcom pseudocode that drops register arguments, the x87 control word
    0x0c7f, a variable frame step in replays, 6-bit palettes shifted by 2, light tables with an
    unchanged level, worktrees without the ignored folders and Ghidra's project lock, and
    reading the story. The entries on missed functions and on Watcom name exportcov and Hardwar.

  • Know-how entries for GameFrame's own tools: unmapped memory under the emulators, an import
    with no handler, a tool run without the .venv, and Ghidra refusing a path in a dot folder.

  • dos_env has a keyboard: env.press(scan, at, hold) and env.key_script('10:39:3') put
    make and break codes on port 0x60 and raise IRQ 1 through the game's INT 9 handler, which
    acknowledges through port 0x20. The BIOS's INT 9 feeds the INT 16h queue, and INT 16h waits
    for a key still to come. The DOS sample gains wait_keys and a keyboard handler.

  • tools/triage/exportcov.py: how much code a Ghidra export covers, its largest gaps, and the
    likely starts of the functions it missed (call targets, starts after a return or padding),
    written for the batch decompile.

  • pe32_emu: fpu_cw= loads an x87 control word before a call, fpu_modes() lists the cases
    whose result depends on the rounding mode, and until= stops at an address and returns the
    stack there, as for a call to sprintf. The sample's 32-bit DLL gains fix_mul (a magic-number
    fadd) and report (a call to wsprintfA).

  • validate.py --write: decode, encode and compare every file, naming the first differing byte.
    The sample decoder has an encode.

  • tools/live/memlog.py (read a recording back: summary, CSV), tools/live/emulock.py (who
    uses a shared emulator) and tools/live/loopcut.py (a recorded path cut into one loop).

  • Thirty-five know-how entries promoted from the POD: Planet of Death project. They cover
    replacement card libraries under Wine (Glide 2, the S3D Toolkit, ViRGE surfaces, missing
    card libraries), mods (the install folder first, values replaced at run time, mods in a
    reference recording),
    recording a run from memory and replaying it (samples that straddle frames, stand-ins for
    unported logic, a game clock that is not real time, waiting for a phase), x87 rounding and
    truncating helpers, functions Ghidra missed and claims that need an address scan, editors
    and writers (one edit type, float views, derived geometry data, round trips of every file,
    axes measured in the running game), input drivers (held keys, click timing, closed racing
    lines), shared VMs (pkill, sudo and DISPLAY), keeping every dialog, and the emulated-PC
    route (CD drives under Wine on Linux, Xvfb and a window manager, SSH, a script that shadows a
    standard module). The card builds' context now says to measure what the card is given.

  • tools/triage/refs.py: every instruction that refers to an address, found in the code bytes
    (32-bit immediates and displacements, 64-bit RIP-relative). Each hit is marked as a read, a
    write or a value. Hits outside any function are marked too. The sample's 32-bit DLL has a
    table with two writers to test it.

  • tools/live/memrec.py: snapshots of a running game's writable memory for memscan.py. It
    also records named values and arrays as JSON Lines. It reads /proc/PID/mem. --wait waits
    for a phase.

  • discinfo.py --iso: the data track of a BIN/CUE as a plain ISO.

  • tools/live/emupc.py: the files that run the original in an emulated PC (QEMU, a Debian
    cloud image, cloud-init with Wine and 32-bit Mesa, Xvfb, openbox, SSH, the disc as a CD-ROM).

  • tools/live/shot.py: captures kept under names of their own, with their text. It never
    overwrites a capture.

  • tools/live/modfolder.py: install, list and clear a folder of changed files in an install
    folder.

  • winput: keys held by down are released at exit. New steps release, point and
    button, and a new option --hold MS.

  • A case study of POD: Planet of Death.

  • Know-how: pgrep-wait-matches-itself. A wait loop with pgrep -f never ends, because the
    pattern matches the waiting shell's own command line.

  • re-observe: a capture script prints its usage for --help and runs nothing, and never
    overwrites an earlier capture. Seen when checking a script's usage started a 15-minute run
    over the saved data.

  • gameframe progress: every agent keeps a progress log, one line per step, in
    derived/progress/<agent>.log (gameframe progress <agent> --add TEXT). gameframe progress
    shows each log's last lines, newest first, so the main session can answer "how is it going?"
    without reading an agent's transcript. Each agent's file has a Progress section that says
    when to write.

  • Know-how: a taxonomy of failure modes and context, one Markdown entry per failure, each with
    its symptoms, cause, evidence, status (solved, workaround, dead end, open) and what was tried
    (the design, the entries).
    gameframe advise "<error text or topic>" finds entries by error signature, facet, alias and
    word. It reads a log on stdin. --check checks every entry. It reads a project's own
    docs/knowhow/ too.

  • A Claude Code hook, scripts/hooks/advise.py. It names the matching entries after a shell
    command (PostToolUse and PostToolUseFailure), once per session. It names the context
    entry for Wine when a command starts Wine.

  • Eighteen seed entries from the POD: Planet of Death project. The failure entries cover
    privileged port probes under Wine, DirectDraw mode tables, windowed mode on a 16-bit desktop,
    16-bit back surfaces, "insert the CD", ini paths, VirtualSize 0, SuspendThread, DirectInput
    focus, input inside Wine, DYLD_* on macOS, wineserver -w, leftover processes, winedbg,
    and the open wow64cpu fault under Rosetta with every attempt. The context entries cover
    Windows 95 DirectDraw games, Wine on macOS and Watcom programs.

  • The know-how hook for Codex (PostToolUse), Gemini CLI (AfterTool), Cursor (postToolUse)
    and GitHub Copilot (postToolUse, postToolUseFailure).
    scripts/hooks/gates/post_events.py reads and answers each agent in its own format. A plugin
    project runs the hook through ./gameframe advise-hook AGENT.

  • gameframe serve, GameFrame's own MCP server, with the tools advise, knowhow_entry,
    knowhow_list and find. .mcp.json names it gameframe.

  • tools/live/winput.py and winput.c, a small Windows program built with zig. It sends keys
    as scancodes, relative mouse moves and clicks to a game from inside Wine. A --dry-run build
    serves the tests.

  • Context entries for DOS games in DOSBox-X, games with a build per 3D card, and 16-bit
    Windows programs.

  • The skills (re-start, re-observe, re-land's new "Know-how" step), every agent and the
    project template suggest gameframe advise when a command fails or the work turns to a new
    era, runtime or hardware.

  • Two hardware guides from the POD: Planet of Death (1997) project:
    MMX and other SIMD code (finding hand-written MMX routines,
    matching each to its plain version through its callers, measuring both, porting exactly) and
    The card makers' 3D APIs (Glide 2.x, ATI3DCIF and the S3D
    Toolkit: reading their state calls, how textures reach each card, games with one build per
    card, and renderer profiles for viewers).

  • Two crates: gameframe-mmx, the MMX instructions bit for bit, and gameframe-gpu-1997, the
    texture formats and API constants of Glide 2.x (with NCC), ATI3DCIF and the S3D Toolkit,
    with one texel decoder.

  • Two specialist task agents: re-vectoriser (SIMD code and CPU builds) and re-accelerator
    (3D card APIs and per-card builds).

  • tools/emu/pe32_emu.py runs routines of 32-bit (x86) Windows games under Unicorn, with
    inputs in registers or on the stack, MMX included. It stops at any import without a handler
    and names it. The sample game builds a 32-bit DLL (mixer32.c) to test it.

  • tools/triage/insns.py lists the pattern for MMX code among its groups.

Changed

  • The plugin has an icon, .claude-plugin/icon.png, and the tests read the plugin validator's
    own warning counts, allowing only GameFrame's own CLAUDE.md (Claude Code 2.1.310).

  • One GameFrame checkout per machine. A project made by new-project.sh pins it with
    GAMEFRAME_ROOT in its .env, and ./gameframe reads that from the project and from its main
    checkout, so worktrees use the same one. The launcher no longer looks in ../game-frame next
    to a project, where projects side by side shared a stale symlinked clone. A game's session
    changes GameFrame in a worktree of that checkout, never in a second clone
    (environments).

  • ExportAddressBatch (ghidra-run.sh decompile) reports a bad address and goes on, and makes
    no function at an address that nothing refers to unless it follows a ret or padding, so a
    call site no longer splits the function around it.

  • The know-how hook reads only stdout, stderr and the error of a Claude Code command. The
    desktop app adds notes on changed files to the result. Those notes had matched the text of
    the entries themselves.

  • peinfo --imports, --exports and --strings list their items again. The section loop had
    reused the name of the flags.

  • The hook matches context patterns against the first line of a command only.

  • A project's entry replaces GameFrame's entry of the same id.

  • peinfo and vtable use a section's raw size when its VirtualSize is 0, as Watcom's linker
    writes it. peinfo marks such a section.

  • re-site, re-publisher, re-land and docs/OUTPUTS.md now allow screenshots of the
    original game and of the project's viewer in documents and on the site, a few at a time,
    each captioned with its source. The game's files, extracted assets in bulk and decompiled
    code still stay out of git.

Fixed

  • pe32_emu zeroes a section whose file pointer is 0, as Windows does. Watcom marks .bss this
    way, and the emulator had filled it with the file's first bytes, so flags read as code
    (from Hardwar). It also starts the x87 at the control word 0x037f, as Windows starts a program;
    Unicorn alone starts at 0, which rounds every result to 24 bits (from POD).

  • Thirteen know-how entries from POD and Hardwar: the AI's chance from the frame clock, a missed
    key tap, a 3D card that draws part of the frame, measuring library calls that lock mutexes, a
    screen that closes while a key is held, decompiled switches that drop cases, painter's order
    with big faces, a pointer the game draws, recording sound in a VM with no sound card, a
    replay that plays too fast, the emulator's x87 start state, one multiply for two channels,
    and the .bss file pointer. The taxonomy gains the symptom wrong-result.

  • tools/site/check.py and the gates no longer take the English word "undefined" in prose for
    decompiled code. The pattern now matches Ghidra's sized types, pointers and variables of
    those types, and statements with FUN_ and DAT_ names (calls, assignments and
    declarations). Names cited in a sentence pass, as before.

  • The animated diagrams: in every animated diagram the static labels are drawn last, so a
    moving token or highlight passes under the text it crosses instead of hiding it. Tokens that
    loop start with a negative begin, so none waits in the card's top-left corner before its
    path starts. Each "original" label in the function-swap diagram hides while its new tile
    shows. The raster-split and Atari 2600 beams now keep step with what they trigger, and the
    raster scroll loops with no seam.

  • site/diagrams/animcheck.py (run by the tests) fails on loops that drift apart, animations a
    browser would drop and elements shown before their turn. site/diagrams/animrender.html
    plays every animated diagram in a browser and lists shapes over labels, tokens off the card
    and boxes that light up when the playhead is elsewhere.

  • readseg.py stops with a clear message when an argument is not an address, such as a count
    written without --count. It printed the first value and then crashed.

  • The Claude Code commit and push gates check the command's own git verbs. Claude Code's
    if: Bash(git commit *) also matches commands it cannot parse (a heredoc, a shell variable,
    a for loop), so the gates fired on commands that neither commit nor push.

  • ./gameframe run decomp_query without --export reads the project's analysis/export
    (the folder GAMEFRAME_PROJECT names), and its labels/names.tsv after the current
    folder's. Before, it read GameFrame's own sample export.

  • dosinfo.py --pstrings prints a Pascal string's length byte address, the address code
    uses, instead of the text's. It finds a string right after another one when the second's
    length byte is a printable character.

  • tdinfo.py: types and globals print the fields of anonymous records inside a type or
    a variable's declaration. labels leaves out imported procedures, whose segment numbers
    are past the program's (procs marks them). A scope's parameters and locals go to the
    procedure in the scope's own segment, not the first procedure at the same offset in any
    segment.