A companion note to MacNetScan, where an animated About-box logo was promised and then defeated: every decoder on the host refused the supplied GIF, so the motion was faked in QuickDraw over a static picture. This note records the sequel. A decodable copy of the same artwork finally arrived, and the animation was done for real — sixteen true frames of a pixel-art title card, decoded and packed on the host, blitted frame by frame inside the About box of a System 7 application that was itself written and compiled in a 1994 THINK C IDE, driven end to end across the AppleBridge with no hands on the keyboard.

Well, that is not entirely precise, of course. - The system had its tools and functionality. But like a small kid initial instructions were necessary.

[1] It stared at it’s random mouse position. Mouse key already pressed, it moved the mouse to the about box and nothing did happen. I instructed the sequence, the system took a note (persistency I still need to check, because it was last night) and ’learned’ that part of the mouse button usage.

[2] In THINK C, clicking the menu button ‘Source’ and selecting ‘Compile’ has also it’s sequence. For us a very intuitive task, the system needs to learn it again, that you move the mouse pointer to the estimated position (screenshot), press the mouse button and move to the estimated position of ‘Compile’, release the mouse button and screen shot to verify expected result.

[3] There are better ways to reach the goal as the key combination + R (here I assume run the code) . This is something that is more easy (no screenshot) and the system did also learn, that there is a cheaper way to a run a program.

MPW and its ToolServer were an easy inroad, as its UNIX style syntax did allow Claude Code to access a broad variety of system functionality plus Assembler and C compiler access.

Still I did grow-up with THINK-C and ever loved its THINK Reference companion. So I considered mouse support and reading menu structures as a must have feature. I never thought about this before and now I am deeply impressed what impact the PARC (Palo Alto Research Center) gave to my previous employer Bill Gates and also Steve Jobs.

The result, playing on the emulated Macintosh:

The About box of ClaudeApp animating on the emulated Macintosh — sixteen frames of the party title card, blitted by CopyBits on a timed WaitNextEvent tick

From a broken GIF to real frames

The earlier attempt failed for a mundane reason: the GIF was corrupt, and ImageMagick, Pillow and sips all declined to coalesce its frames. The workaround then was to synthesise motion — a sweeping beam, a few drifting packets — over the one static image that did decode. Useful as a fallback, but not the thing itself.

This time the file was intact: a 960×360, forty-frame GIF89a of Claude Code and a Macintosh SE/30 exchanging hexadecimal packets across a bridge — the project’s own magic numbers, in fact (601A4D53 is the Route B trap-patch signature; ABrg is the daemon’s creator code; 9001 is the control port). With a source that decodes, the plan is straightforward in outline and entirely in the details: reduce the artwork to something a 68K Macintosh can hold and draw, carry it across the bridge, and play it inside a real Toolbox window.

The host-side pipeline

A 68K Macintosh at 8-bit colour cannot spare megabytes for an animation, so the frames are prepared on the host by a small tool (host/tools/gif_to_rez.py) before they ever reach the guest:

  1. Coalesce and scale. ImageMagick disposal-coalesces the GIF and hard-scales every frame to 320×120 — an 8:3 strip that keeps the original aspect ratio and fits comfortably inside an About box.
  2. One shared palette. Sixteen frames are sampled evenly from the forty and quantised against a single, shared 256-entry colour table, so colours stay stable from frame to frame with no inter-frame flicker. The pixel art turned out to use only 25 distinct colours.
  3. Pack each row. Every scanline is PackBits-compressed — the classic Macintosh run-length encoding that UnpackBits reverses in the Toolbox. Against a mostly-flat dark background of 25 colours, this is dramatic: the raw pixel budget of 602 KB drops to 89 KB (14.5%). Each compressed frame round-trips through a reference UnpackBits in the tool itself, so a packing bug cannot reach the device silently.
  4. Emit resources. The palette becomes a 'clut' resource; each frame a 'Gfrm' resource; a small 'GFin' record carries the count, dimensions, base id, frame delay and a packed-flag. The whole animation is one self-contained Rez source that the ToolServer merges into the built application with Rez -a.

Blitting on the 68K side

Inside the application the frames are drawn with Color QuickDraw. The sixteen 'Gfrm' resources and the 'clut' are loaded once; a single off-screen PixMap is built by hand over a 38 KB frame buffer, its pmTable pointing at the loaded colour table and the high bit of rowBytes set to mark it a pixel map rather than a bitmap. On each beat, one frame is unpacked row by row into that buffer and CopyBits transfers it into the window.

The one subtlety that matters is the loop that drives it. A naïve modal wait — spin on Button(), call SystemTask() — reads the hardware mouse and starves every background process. On this system that background process is the AppleBridge daemon, so the tight spin froze the bridge: screenshots timed out, commands went unanswered. The fix is the cooperative idiom the classic Toolbox was built around:

The animation is driven from WaitNextEvent with a sleep quantum equal to the frame delay. Between frames the CPU is yielded to background processes — the daemon keeps its scheduling, the bridge stays responsive — and because the loop consumes the event queue rather than polling hardware, a synthetic mouse-down dismisses the box exactly as a real click would.

That single change is the difference between an animation that hangs the emulator and one that can be watched, screenshotted and dismissed entirely over the wire.

Building it in THINK C, over the bridge

The harder half of the exercise was not the code but the compiler. Unlike MPW, which is a scriptable command-line toolchain, THINK C is a graphical IDE with no command line at all — there is nothing to send it a Build verb. So the IDE itself was driven across the bridge: the source was edited on the host and pushed to the project folder, and the build was performed by injecting input into the running IDE. The project’s main.c was opened, Project ▸ Build Application was pulled down, the bring up to date prompt answered with Update, a name typed into the Standard File dialog, and Save clicked — every step a real mouse movement or an injected keystroke, verified by screenshot. The freshly built application then had its 'clut'/'GFin'/'Gfrm' frames merged in with Rez -a, and was launched over the bridge.

Four things were learned the hard way and are worth recording:

  • Move before you click. A synthetic click that teleports the cursor is unreliable; moving the pointer to the target and letting it settle before pressing lands the click where intended.
  • One gesture per menu. A classic pull-down menu is tracked while the button is held. The press, the drag to the item and the release must be a single uninterrupted gesture — split across separate calls, the button lifts between them and the menu closes.
  • Modal loops starve the daemon. MenuSelect, ModalDialog and the Standard File dialog all run tracking loops that do not yield enough for a heavy daemon-side screenshot. While one is open, a host-side window capture is the reliable way to see the screen.
  • Mind the editor buffer. A stray click that lands in the source view types into the unsaved editor buffer, not the file on disk; a single spurious token there produced a puzzling “invalid declaration”. Closing the document without saving discarded the buffer and the clean disk file compiled first time.

The result

ClaudeApp opens, and its About box plays the party: Claude Code and the SE/30 trading hex packets across the bridge, the little machine’s screen filling and its face smiling, all at roughly ten frames a second — sixteen real frames, no synthesised motion, blitted by CopyBits and yielding politely to the daemon between each. The image at the top of this page is that box, captured over the bridge and reassembled from the exact frame data compiled into the application.

The complete source

The application in full — a single THINK C source file. The animation machinery is GifLoad, GifDrawFrame and the WaitNextEvent loop in ShowAbout; the rest is an ordinary System 7 event shell.

/* ClaudeApp - a windowed Macintosh application.
 * Written and built by Claude over the AppleBridge, driving a 1994
 * THINK C IDE on System 7.6.1.  Apple menu -> About for the story.
 *
 * The About box plays a real animated logo: 16 frames of the "HEX OVER THE
 * BRIDGE" party GIF, downscaled to 320x120 and stored as PackBits'd 'Gfrm'
 * resources with a shared 'clut'.  Each tick we UnpackBits one frame into an
 * offscreen PixMap and CopyBits it into the window - just text + the
 * animation, no hand-drawn graphics.  Cheers!  - Claude
 */

#define kApple  128
#define kFile   129
#define kAbout  1
#define kQuit   1
#define kBase   128        /* resource id of 'clut'/'GFin'; 'Gfrm' = baseID.. */

#define AUTO_ABOUT 1       /* TEST: open the About box on launch (set 0 for release) */

WindowPtr  gWin;
Boolean    gDone = false;

/* ---- animated About-box logo (the party GIF) -------------------------- */

typedef struct {
    short count;    /* number of frames            */
    short w;        /* frame width  (== rowBytes)  */
    short h;        /* frame height                */
    short baseID;   /* 'Gfrm' id of frame 0        */
    short delay;    /* ticks between frames        */
    short packed;   /* 1 => rows are PackBits'd    */
} GifInfo;

static GifInfo    gGif;
static Boolean    gGifReady = false;
static CTabHandle gClut     = 0L;
static Ptr        gFrameBuf = 0L;      /* one unpacked frame, w*h bytes */
static PixMap     gSrcPM;              /* source pixmap over gFrameBuf   */
static Handle     gFrames[64];         /* the 'Gfrm' resources           */

static void GifLoad(void)
{
    Handle info;
    short  i;

    if (gGifReady) return;

    info = GetResource('GFin', kBase);
    if (info == 0L) return;
    BlockMove(*info, &gGif, (long)sizeof(GifInfo));
    if (gGif.count <= 0 || gGif.count > 64) return;

    gClut = (CTabHandle) GetResource('clut', kBase);
    if (gClut == 0L) return;
    HNoPurge((Handle)gClut);

    for (i = 0; i < gGif.count; i++) {
        gFrames[i] = GetResource('Gfrm', gGif.baseID + i);
        if (gFrames[i] == 0L) return;
        HNoPurge(gFrames[i]);
    }

    gFrameBuf = NewPtr((long)gGif.w * (long)gGif.h);
    if (gFrameBuf == 0L) return;

    gSrcPM.baseAddr   = gFrameBuf;
    gSrcPM.rowBytes   = (short)(gGif.w | 0x8000);   /* high bit => PixMap */
    SetRect(&gSrcPM.bounds, 0, 0, gGif.w, gGif.h);
    gSrcPM.pmVersion  = 0;
    gSrcPM.packType   = 0;
    gSrcPM.packSize   = 0;
    gSrcPM.hRes       = 0x00480000L;                /* 72 dpi */
    gSrcPM.vRes       = 0x00480000L;
    gSrcPM.pixelType  = 0;                          /* chunky */
    gSrcPM.pixelSize  = 8;
    gSrcPM.cmpCount   = 1;
    gSrcPM.cmpSize    = 8;
    gSrcPM.planeBytes = 0;
    gSrcPM.pmTable    = gClut;
    gSrcPM.pmReserved = 0;

    gGifReady = true;
}

static void GifDrawFrame(WindowPtr w, short idx, Rect *dst)
{
    Handle   h;
    Ptr      src, dp;
    short    row;
    RGBColor savedFore, savedBack;

    h = gFrames[idx];
    if (h == 0L) return;

    HLock(h);
    src = *h;
    dp  = gFrameBuf;
    if (gGif.packed) {
        for (row = 0; row < gGif.h; row++)
            UnpackBits(&src, &dp, gGif.w);
    } else {
        BlockMove(src, gFrameBuf, (long)gGif.w * (long)gGif.h);
    }
    HUnlock(h);

    GetForeColor(&savedFore);
    GetBackColor(&savedBack);
    ForeColor(blackColor);
    BackColor(whiteColor);
    CopyBits((BitMap *)&gSrcPM,
             (BitMap *)*(((CGrafPtr)w)->portPixMap),
             &gSrcPM.bounds, dst, srcCopy, 0L);
    RGBForeColor(&savedFore);
    RGBBackColor(&savedBack);
}

void ShowAbout(void)
{
    WindowPtr   w;
    Rect        r, gif;
    EventRecord ev;
    short       fr;
    Boolean     done;

    SetRect(&r, 80, 60, 520, 380);               /* 440 x 320 */
    w = NewCWindow(0L, &r, "\pAbout ClaudeApp", true, dBoxProc,
                   (WindowPtr)-1L, false, 0L);
    SetPort(w);
    TextFont(3);        /* Geneva */
    TextSize(10);

    MoveTo(26, 34);  TextSize(14); DrawString("\pClaudeApp");  TextSize(10);
    MoveTo(26, 62);  DrawString("\pA windowed Macintosh application.");
    MoveTo(26, 90);  DrawString("\pWritten and built by Claude, an AI from 2026,");
    MoveTo(26, 108); DrawString("\pdriving a 1994 THINK C IDE on System 7.6.1");
    MoveTo(26, 126); DrawString("\pthrough the AppleBridge - no hands on the keyboard.");
    MoveTo(26, 296); DrawString("\p(click anywhere to close)");

    SetRect(&gif, 60, 150, 380, 270);
    GifLoad();
    ValidRect(&w->portRect);   /* clear any pending update so WNE can sleep */

    /* Cooperative animation loop: WaitNextEvent yields the CPU to background
     * processes (the AppleBridge daemon!) between frames, so the bridge stays
     * responsive - and a synthetic mouseDown dismisses it like a real one. */
    fr   = 0;
    done = false;
    while (!done) {
        if (gGifReady) {
            GifDrawFrame(w, fr, &gif);
            if (++fr >= gGif.count) fr = 0;
        }
        if (WaitNextEvent(mDownMask | keyDownMask, &ev,
                          gGifReady ? (long)gGif.delay : 30L, 0L)) {
            if (ev.what == mouseDown || ev.what == keyDown)
                done = true;
        }
    }
    DisposeWindow(w);
    SetPort(gWin);
}

void DoMenu(long pick)
{
    short  menu = HiWord(pick);
    short  item = LoWord(pick);
    Str255 nm;

    if (menu == kApple) {
        if (item == kAbout) ShowAbout();
        else { GetItem(GetMHandle(kApple), item, nm); OpenDeskAcc(nm); }
    } else if (menu == kFile) {
        if (item == kQuit) gDone = true;
    }
    HiliteMenu(0);
}

void main(void)
{
    EventRecord ev;
    WindowPtr   who;
    MenuHandle  m;
    Rect        r;
    short       part;

    InitGraf(&thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(0L);
    InitCursor();

    m = NewMenu(kApple, "\p\024");
    AppendMenu(m, "\pAbout ClaudeApp...;(-");
    AddResMenu(m, 'DRVR');
    InsertMenu(m, 0);
    m = NewMenu(kFile, "\pFile");
    AppendMenu(m, "\pQuit/Q");
    InsertMenu(m, 0);
    DrawMenuBar();

    SetRect(&r, 50, 60, 500, 330);
    gWin = NewWindow(0L, &r, "\pClaudeApp", true, documentProc,
                     (WindowPtr)-1L, true, 0L);
    SetPort(gWin);

#if AUTO_ABOUT
    ShowAbout();       /* TEST: pop the About box straight away */
#endif

    while (!gDone) {
        if (WaitNextEvent(everyEvent, &ev, 20L, 0L)) {
            switch (ev.what) {
            case mouseDown:
                part = FindWindow(ev.where, &who);
                if (part == inMenuBar)
                    DoMenu(MenuSelect(ev.where));
                else if (part == inDrag)
                    DragWindow(who, ev.where, &screenBits.bounds);
                else if (part == inGoAway) {
                    if (TrackGoAway(who, ev.where)) gDone = true;
                }
                break;
            case keyDown:
            case autoKey:
                if (ev.modifiers & cmdKey)
                    DoMenu(MenuKey((char)(ev.message & charCodeMask)));
                break;
            case updateEvt:
                who = (WindowPtr)ev.message;
                BeginUpdate(who);
                SetPort(who);
                EraseRect(&who->portRect);
                if (who == gWin) {
                    TextFont(3); TextSize(12);
                    MoveTo(30, 52);  DrawString("\pHello from THINK C - a windowed app!");
                    MoveTo(30, 88);  DrawString("\pWritten and built by Claude over the");
                    MoveTo(30, 108); DrawString("\pAppleBridge, driving the THINK C IDE.");
                    MoveTo(30, 152); DrawString("\pApple menu  ->  About ClaudeApp   for the party.");
                    MoveTo(30, 186); DrawString("\pFile menu  ->  Quit  (Cmd-Q)  to exit.");
                }
                EndUpdate(who);
                break;
            }
        }
    }
}

Status: built and verified live on the System 7.6.1 stack. Frames prepared by host/tools/gif_to_rez.py; merged with Rez -a; the application compiled in THINK C and launched entirely over the AppleBridge. The animated figure is the real 'Gfrm' frame data composited into a native screenshot of the running About box.