SheepShaver 8.6 UI oracle¶
Use this lane for fast UI iteration at NOW's minimum Carbon floor: PowerPC CFM, Mac OS 8.6, and CarbonLib 1.6. It runs the real guest application and the real classic Toolbox. It is therefore a substantially better UI oracle than the host renderer, but it is still emulator-observed rather than metal-verified.
Keep the roles distinct:
- SheepShaver 8.6 plus CarbonLib 1.6 is the default Carbon UI oracle.
- QEMU
mac99remains the full-stack lane and the explicit Mac OS 9.1+ reference. - Basilisk II is the future profile-driven 68K UI lane. Apply the isolation, transfer, identity, and evidence patterns from this harness when it lands.
What the repository owns¶
The repository owns only the harness and its tests. It does not fetch or
redistribute SheepShaver, a New World ROM, Mac OS, or CarbonLib. Put paths to
developer-supplied inputs in .env.lab; never add those inputs to the tree.
The first verified host build used the maintained
kanjitalk755/macemu tree at commit
efefb991ecfff91e9f5b0e63a52cfecabfee3aae (2026-08-09). On Xcode 27,
Autoconf selected GNU C23 by default and the bundled slirp K&R definitions
failed in slirp/ip_output.c. Pinning C17 was the required build seam:
cd SheepShaver/src/Unix
env CFLAGS=-std=gnu17 ./configure \
--with-gtk=no --disable-jit --enable-ppc-emulator=yes \
--enable-sdl-video --enable-sdl-audio
make -j8
make SheepShaver_app
scripts/package-sheepshaver-86 \
"path/to/SheepShaver.app" "path/to/SheepShaver-8.6-Oracle.app"
That recipe produces a native arm64 app initially linked to Homebrew's SDL
compatibility and VDE libraries. package-sheepshaver-86 copies the complete
dependency closure into Contents/Frameworks, rewrites Mach-O load paths,
embeds the SheepShaver license and source revision, fixes the bundle identifier,
ad-hoc signs the result, and refuses a package whose final load commands still
name Homebrew or /usr/local. It publishes atomically so a late failure cannot
leave a partial app at the destination.
Homebrew's SDL2 may be sdl2-compat, which opens SDL3 at runtime rather than
recording it in Mach-O load commands. The packager detects that loader marker,
bundles libSDL3.dylib explicitly, and refuses the package if the runtime SDL3
is absent. An otool -L-only closure is not sufficient for this dependency.
This makes the local oracle host app self-contained. It does not turn the ROM, Mac OS, CarbonLib, or the profile into redistributable inputs, and it does not settle whether a modified GPL emulator should become a shipped product dependency. Those remain separate decisions.
Profile contract¶
Use one dedicated .sheepvm directory containing:
Mac OS 8.6.sheepvm/
prefs
Mac OS ROM
Mac OS 8.6.hfv
CarbonLib 1.6 Transfer.hfv
Shared/
Snapshots/
The verified profile uses a 2 GiB HFS+ boot disk, a 32 MiB HFS transfer disk, 128 MiB RAM, an 800 by 600 software-rendered window, slirp networking, and JIT disabled. Preserve a stopped, clean base-OS snapshot before installing CarbonLib, then another after CarbonLib 1.6 is proven by the NOW guest.
Record ROM and installation-media hashes in the local run receipt. A filename or a booting desktop does not prove which inputs produced the guest.
Operate it¶
Configure .env.lab, then run:
scripts/sheepshaver-86 doctor
scripts/sheepshaver-86 stage "path/to/New Old World.bin"
scripts/sheepshaver-86 install-apps \
"path/to/New Old World.bin" "path/to/CodeKitten.bin"
scripts/sheepshaver-86 launch
scripts/sheepshaver-86 capture "path/to/evidence/frame.bmp"
scripts/sheepshaver-86 input "move 200 120" "down 0" "up 0"
scripts/sheepshaver-86 snapshot "Mac OS 8.6 + CarbonLib 1.6"
scripts/sheepshaver-86 rig
scripts/sheepshaver-86 seal "os86-carbon16-now020-codekitten-5d404f7-rgbv2"
scripts/sheepshaver-86 clone \
"os86-carbon16-now020-codekitten-5d404f7-rgbv2" \
"/absolute/path/to/finder-run.sheepvm"
stage accepts a MacBinary artifact and passes it to hcopy -m, preserving
both forks plus Finder type and creator. It refuses while SheepShaver has the
transfer image open. Never host-mount or host-write an HFS image concurrently
with the guest.
rig also requires a stopped guest. It emits a versioned record containing
the NOW revision and SHA-256 identities for the emulator executable, profile
preferences, ROM, boot disk, and transfer disk. If
NOW_SHEEPSHAVER_SOURCE is configured, it includes the exact macemu revision.
Attach that output to captures rather than reconstructing the rig from memory.
clone makes an isolated run profile from a named sealed rig. A current seal
freezes the boot disk, transfer disk, preferences, ROM and installed-app
manifest beside the receipt, with one SHA-256 for each. clone verifies and
copies those versioned inputs, uses copy-on-write clones where the filesystem
supports them, and records the source receipt as Oracle Parent.rig. Later
staging or preference changes in the live profile therefore cannot invalidate
or silently alter an older oracle. Legacy receipts remain cloneable only while
their mutable inputs still match the hashes they originally recorded. Run
visual experiments in a clone rather than changing the sealed profile. The
command refuses a relative destination, an existing destination, a live source
image, or any sealed input whose bytes no longer match its receipt.
install-apps writes both MacBinary applications into the configured
Applications folder while the guest is stopped. It refuses a live boot disk,
preserves both forks, and lists the resulting Finder identities before
unmounting. seal creates the complete immutable-named input set and matching
.rig receipt transactionally; failure removes partial sidecars, and an
existing member is never replaced. A named boot snapshot without those frozen
companions and its receipt is not a current versioned oracle.
Before the boot disk is opened, the installer verifies the MacBinary header
CRC, fork lengths, and internal Finder identity. NOW must be New Old
World/APPL/NOWo; CodeKitten must be codekitten/APPL/O9ID. A plausible
filename cannot substitute another build or a truncated fork envelope.
Installed application set¶
The 8.6 oracle carries two project applications for different reasons:
- New Old World is the application under test. A visible Workshop after a clean reboot is the positive CarbonLib and minimum-floor UI assertion.
- CodeKitten is a second application under test. Its IDE shell supports Mac OS 8.6 through 9.2.2 with CarbonLib 1.6, although an individual toolchain or package may declare a higher floor. It must launch and remain responsive in this profile. Failure is a CodeKitten compatibility bug to capture with the exact artifact and rig identities; it must not be reclassified as an expected limit of the 8.6 oracle.
Record both MacBinary SHA-256 values and source revisions in the sealed-version receipt or companion evidence. Do not let a convenient old artifact silently become the oracle's CodeKitten.
Do not run classic applications or installers from SheepShaver's Unix shared
folder. The CarbonLib SMI wrapper reached the Finder through ExtFS but failed
with error -8812: the file system cannot carry the execution semantics it
requires. A real HFS transfer disk is the reusable boundary.
CarbonLib installation has two layers¶
Opening CarbonLib 1.6.smi and accepting its license only mounts the
self-mounting image. Open the mounted CarbonLib disk and run
Apple SW Install; that second application performs the system update. The
decisive negative control was the NOW guest itself: before the second updater
ran, Finder refused it because CarbonLib could not be found.
After the updater finishes, shut the guest down cleanly, relaunch it, and open
the staged New Old World. Seeing the Workshop proves the application loaded
against CarbonLib in this profile. Then launch CodeKitten and exercise its
initial window and one menu. A failure to launch or remain responsive is a bug
in the supported 8.6 runtime, not a reason to move the declared floor. Neither
observation proves hardware behavior.
The first acceptance on 2026-08-11 reached the NOW Workshop. CodeKitten did
not reach its initial window, so the menu step is blocked by a product bug. The
first artifact imported MLTE from a standalone Textension fragment that is
absent from the installed CarbonLib 1.6. A repaired artifact imports the 32
MLTE symbols from CarbonLib's embedded Textension_CL fragment, but the Mac OS
8.6 CFM loader still reports that CarbonLib cannot be found. Read-only disk
inspection proved that the installed data/resource forks exactly match that
artifact and that the system file is CarbonLib Update 1.6; static import-name
comparison found no missing strong or weak symbol in CarbonLib's main
fragment. The remaining cause is unresolved CFM load/dependency behavior, not
a stale application, a skipped updater, or an unsupported runtime. See
docs/open-issues.md for the live bug record.
UI automation seams¶
The guest framebuffer exposes no useful macOS accessibility tree. Automation is screenshot-and-coordinate driven and must re-observe after any human input. At 800 by 600, these interactions were repeatable during bring-up:
- Open a Finder item by selecting it, refreshing observed state, then sending Command-O. Synthetic double-click and Return were unreliable.
- Stabilize text-field focus with a click and fresh observation before typing; otherwise the first characters can be dropped.
- Shut down from Finder by dragging from the Special menu title to Shut Down, then clicking the selected item immediately, without an observation between the drag and click. A state refresh dismisses the menu.
Coordinates are profile-specific evidence, not a durable API. The harness should grow toward semantic guest-side controls or deterministic framebuffer recognition before unattended installation is treated as reliable.
Native framebuffer capture and input¶
The versioned local macemu branch codex/now-oracle-capture adds bounded,
profile-local request seams. scripts/sheepshaver-86 capture creates a sentinel
in the active .sheepvm; the SDL renderer answers with a BMP of its native
guest surface, writes the exact NOW_ORACLE_CAPTURE_NATIVE_RGB_V2 response,
then removes the request. The versioned response is part of the contract: the
launcher refuses an older capture build that publishes a BMP without the RGB
receipt, because the original implementation preserved framebuffer geometry
but exchanged the red and blue channels. scripts/sheepshaver-86 input publishes
validated native-pixel mouse actions or raw ADB key transitions atomically and
requires the renderer's completion receipt. Both commands time out rather than
accepting stale state. Captures exclude macOS window chrome, scaling, and the
host cursor.
An input receipt proves that the renderer dispatched the ADB transitions; it
does not prove that the cooperative guest event loop consumed them. Immediate
follow-up input during bring-up retained mouse-button or modifier state and
caused Finder drags and multi-selection. The harness therefore waits one
second after a successful renderer receipt by default. Override that with
NOW_SHEEPSHAVER_INPUT_SETTLE only for a measured profile, and use a capture
after the settle interval as the behavioral observation.
The seam deliberately does not replace SheepShaver's renderer:
main_unix.cpprecognizes a.sheepvmdirectory as a first-class launch argument, changes into it, and reads that profile's preferences. The isolated profile convention is native behavior, not a launcher accident.video_sdl2.cppalready ownsguest_surface,host_surface, and the exact dirty rectangle immediately beforeSDL_RenderPresent; the hook reads that existing surface only after an explicit profile-local request.- The same file drains SDL keyboard and mouse events into ADB calls. The input seam invokes those ADB calls on the renderer thread, using native guest coordinates and explicit key transitions rather than the macOS pointer.
- The existing
--gui-connectionRPC is not that control plane. It connects the emulator back to the settings UI and currently carries alerts and exit only. noguisuppresses the settings front end; it does not make video headless.
The next useful macemu-side experiment is a loopback-only socket for profile identity and longer-lived control. Keep that as a separately reviewable fork patch until its licensing and redistribution posture are chosen. The current capture/input patch is likewise not an upstream or product dependency; the sealed rig records its exact source and executable identities.
Mirror visual-oracle loop¶
tools/mirror-oracle turns the native framebuffer seam into a repeatable
reference loop. It consumes both existing renderers: SheepShaver produces the
target's real pixels, and mirror-render calls MirrorKitUI's production
RenderShot. There is no third UI implementation in the comparison tool.
List the current profile and cases, prepare a disposable run profile, then capture or compare:
tools/mirror-oracle list
tools/mirror-oracle prepare \
os86-carbon16-now020-codekitten-5d404f7-rgbv2 \
/absolute/path/to/finder-run.sheepvm
tools/mirror-oracle capture finder-desktop \
--vm /absolute/path/to/finder-run.sheepvm \
--apply-case-input \
--out /absolute/path/to/evidence/finder-desktop-reference
tools/mirror-oracle state-template finder-desktop \
--capture-receipt /absolute/path/to/evidence/finder-desktop-reference/capture.json \
--out /absolute/path/to/observed-state.json
tools/mirror-oracle capture finder-desktop \
--vm /absolute/path/to/finder-run.sheepvm \
--apply-case-input \
--state-proof /absolute/path/to/observed-state.json \
--out /absolute/path/to/evidence/finder-desktop
tools/mirror-oracle run finder-front-icon-view \
--vm /absolute/path/to/finder-run.sheepvm \
--state-proof /absolute/path/to/observed-state.json \
--scene /absolute/path/to/scene.json \
--out /absolute/path/to/evidence/finder-icon-view \
--report-only
Build the private OS 8.6 asset pack directly from the stopped SheepShaver volume, then derive the capture-attributed menu-bar art into a new pack:
tools/extract-assets-offline \
--image /absolute/path/to/Mac\ OS\ 8.6.hfv \
--out /absolute/path/to/os86-base/Resources
tools/mirror-oracle extract-chrome platinum.macos-8.6.default \
--guest /absolute/path/to/stable-guest.bmp \
--capture-receipt /absolute/path/to/capture.json \
--base-assets /absolute/path/to/os86-base/Resources \
--out /absolute/path/to/os86-oracle/Resources
The first command recognizes a raw HFS/HFS+ .hfv as well as an APM qcow2
disk. The second clones the whole base pack, writes transparent crops declared
by the visual profile, and publishes manifest.json only after the derived
pack and its provenance receipt are complete. It refuses a framebuffer whose
SHA-256 or profile does not match the capture receipt, and carries that
receipt's identity and evidence status into chrome/provenance.json; a loose
BMP is not an asset source. It never edits the sealed base pack. The BMP
decoder must ignore its zero alpha plane: SheepShaver's native 32-bit
framebuffer stores visible RGB with every alpha byte zero.
A capture is accepted only after two consecutive native framebuffer reads have
the same SHA-256 after case-declared volatile masks are applied. All attempts
remain in samples/; success writes capture.json, and a refusal writes
capture-failure.json. The receipt includes the case, visual profile, NOW
revision, backend doctor output, run-profile identities, exact input actions,
and optional state-proof identity. A capture without --state-proof is
explicitly reference-only. state-template copies the accepted masked
framebuffer digest and every required-state sentence into an intentionally
incomplete observation form; it does not mark anything observed. After an
observer inspects the original-size framebuffer, fills every assertion and
reruns the capture, the tool accepts only an exact schema/case/profile/digest
match, timezone-bearing observation time, named observer, and one observed
assertion with evidence for every required state. Success is
state-proof-validated; an invalid proof writes capture-failure.json and no
success receipt. A required-state sentence or attached file alone is not proof
that the machine was in that state.
Comparisons are exact and region-specific. They write comparison.json, a
same-scale pair.png, and a red-pixel diff.png; a mismatch exits nonzero
unless run --report-only was requested. Whole-screen similarity is not an
acceptance measure. Cases that depend on window geometry resolve their region
from the same scene JSON rendered by MirrorKit.
The first profile is platinum.macos-8.6.default, with twelve broad Finder
and menu-bar cases: resting desktop; icon, selected-icon, Buttons and List folder views;
inactive Finder chrome; and Apple, File, Edit, View, Special and Help menus.
The shared finder-clean-desktop input sequence uses Finder's own
Command-Option-W close-all gesture and then clears desktop selection. This
replaced ten persisted-window close-box clicks and is both faster and
independent of the saved window stack. Every case now has replayable input.
All three view cases and the five core non-Apple menus have been captured
against the running 8.6 profile. Only the resting desktop currently carries a
validated state proof and accepted comparison, so the others remain
reference-only rather than silently becoming acceptance evidence.
The view cases have checked-in semantic calibration scenes derived from
the measured window, scrollbar, item and column-header geometry. Buttons is a
distinct semantic view and renders its 48 by 48 raised wells; it is no longer
collapsed into the small-icon view. It also activates an item on one primary
click rather than retaining a selection like Icons and List. The Icons case
explicitly selects as Icons instead of trusting the folder's saved view, and
the selected-icon case measures the native selection treatment: each icon
colour channel is multiplied by 128/255 while the tight name patch becomes
black with white Geneva text. The render harness applies view and
selection as explicit test overlays because FinderPresentation is
deliberately host-internal and excluded from encoded scene IR. This preserves
the protocol boundary while making the same production RenderShot path
repeatable. The list renderer now draws the Finder-owned item-count strip,
light-grey ruled row field and folder disclosure triangles locally from the
semantic roster. It also draws native-style modification dates and the shared
count-and-available string. Available capacity is optional semantic metadata
from file.listing.freeBytes; it is rendered host-side in every Finder view
and omitted honestly when a responder cannot report it.
The inactive case opens Calculator through the Apple menu and moves its window
clear of the Finder folder. This replaces the original QuickTime route, whose
first-run promotion obscured the very window being measured. The unobstructed
reference shows one activation rule spanning the whole Finder shell: column
headers switch to the measured BB/77/99 face, frame, and shadow ramp; both
scrollbars withdraw their arrows and thumb into a flat EE trough with a 55
frame; and the grow box disappears. The renderer derives those states locally
from the window's semantic front flag. It does not dim the folder contents
and it does not treat a disabled scrollbar in an active window as inactive.
The render-only finderInactive case overlay constructs this calibration state
without adding a field to the scene contract or sending framebuffer pixels.
The first broad comparisons are deliberately mismatch baselines rather than acceptance claims. Against the measured full-window regions, Icons currently differs in 28,573 of 101,844 pixels (28.06%), Buttons in 43,564 (42.78%), and List in 69,640 (68.38%). These are the pre-capacity baselines and have not yet been promoted to acceptance evidence. The remaining residual includes exact custom folder art and some frame/scrollbar geometry, so these numbers are a queue for later narrowing, not a score to optimize blindly. The File menu is already much closer at 10,712 of 94,300 compared pixels (11.36%).
The inactive Finder correction is measured by furniture region rather than by the still-broad full-window residual. Changed pixels fell from 8,547/8,547 to 5,077/8,547 in the column-header band, from 1,255/2,771 to 677/2,771 in the vertical scrollbar, and from 2,772/6,256 to 1,586/6,256 in the horizontal scrollbar. The full named window moved from 65,728/101,844 (64.54%) to 60,495/101,844 (59.40%). The clean capture remains reference-only until its required state is independently observed and attached.
The shared core-menu scene now contains all measured rows, separators,
enablement, shortcuts, checks and hierarchical arrows for File, Edit, View,
Special and Help. Each case selects one menu through render.openMenu, so one
scene exercises the same menu implementation as the live mirror. The clean
native references now pin the complete outer frames for File (154 by 260),
View (179 by 164), Special (120 by 110), Help (140 by 62), and Apple (198 by
384), all beginning at y=19. Apple uses its measured 18-pixel rows and
4-pixel separator, locally joins Apple Menu Items identities to extracted
small icons, and leaves About This Computer iconless as the native 8.6 Finder
does. Edit remains content-derived because its native capture was obstructed;
it is not silently presented as measured.
Cleanup is armed before the first transition and releases every mouse button and common modifier even when capture fails. The SheepShaver backend sends one action per renderer receipt: the guest must receive the harness's settlement interval between a menu-title press, a drag, and a release. Batching those transitions dispatches them all but lets the cooperative event loop miss the gesture.
The QEMU backend remains available with --backend qemu --qmp-socket ... for
the full-stack and Mac OS 9.1+ lanes. QEMU input is intentionally refused here;
drive QEMU through its existing semantic harness. A future visual version gets
another profile and evidence-backed deltas rather than conditionals scattered
through the comparison code.
The first color-correct reference-only calibration used the stable 800×600
Finder capture whose SHA-256 is
cdddab6c4e1de1d43b57580f2c2ccd2e2c0a54d8898ca91910b91e893e2bc09a.
It is not an accepted resting-desktop case: a transfer window remained open
and no state proof was attached. Its unobscured 20-row menu-bar region is still
a valid named visual reference. Against a pack extracted from the same 8.6
boot disk, the production renderer differs in 35 of 13,500 unmasked pixels
(0.259%); all residual pixels are inside the word “View”. The desktop region
that the profile declares as unobscured proves all 69,160 background pixels
exactly against the extracted, origin-zero Mac OS Default tile. This proves
the background asset and tiling rule, not the resting-desktop case: the source
still contains a transfer window and Finder icons while the calibration scene
intentionally contains no semantic windows or desktop items.
The first accepted case supersedes that limitation for the resting desktop.
Its original target BMP SHA-256 is
6867dd8c0c8d09d1efb598c904238a9906f257b63958081e6d44623223aca60a;
its masked framebuffer SHA-256 is
6071bd241484d30c7eb1253d28791aaf7cb508a0bb992cab4d1de73cd9487841.
A second fresh clone, with a different clock and therefore a different file
hash, reached the same masked digest after the case input. The receipt is
state-proof-validated for Finder frontmost, no open menu, no selected desktop
item and no modal. A semantic calibration scene now contains the eight visible
desktop items. Region splitting keeps the unmodeled Control Strip from hiding
the rest of the desktop: the current production render differs by 35/13,500
unmasked menu pixels (0.259%), 7,495/416,000 main-desktop pixels (1.802%),
8,760/22,080 Control Strip pixels (39.674%), and 1,233/25,920 bottom-desktop
pixels (4.757%). The volume fallback now uses the measured 32×10 drive face
near the bottom of its semantic 32×32 Finder cell, removing 2,163 wrong
main-desktop pixels from the prior render without changing hit geometry. Pack
format 0.3.0 then removed another 3,150 by extracting three file-owned custom
icon suites, joining Get QuickTime through its exact TVOD/MooV identity, and
drawing alias labels with the extracted Geneva 9 italic strike. Get QuickTime's
32×32 icon is pixel-exact. Finder resource 8213 resembles an alias badge but
does not match the desktop transform. Instead, extract-chrome derives a
profile-scoped 7×8 overlay only when three independently extracted custom icons
leave the same residual over the proved desktop tile. Their 45, 44, and 45
changed pixels agree on one 45-pixel output (44 common), making all four
32×32 item regions pixel-exact and removing another 295 wrong desktop pixels.
The earlier stable BMP beginning db968889... is invalid as color evidence.
Its source build saved an SDL intermediate surface with masks that did not
describe the host bytes, exchanging red and blue. Any crop or comparison made
from that capture must be regenerated from a V2-receipted framebuffer.
Evidence status¶
Use the repository's verification vocabulary:
- A successful cross-build means the PowerPC guest builds.
- A visible Workshop in this profile is emulator-observed on Mac OS 8.6 with CarbonLib 1.6.
- CodeKitten revision
5d404f7is installed and emulator-observed failing to launch with a CFMCarbonLiberror. It is a supported-runtime bug, not a passed launch or menu acceptance. - Only a run on the PowerBook is metal-verified.
Capture the source revision, profile/input hashes, staged artifact build ID, and a screenshot together. An attractive desktop without those identities is reference material, not attributable verification.
The Finder breadth cases deliberately keep acquisition and rendering separate.
The scene supplies menus, rows, item geometry and guest-owned action identity;
case overlays may select a Finder view or attach attributable catalog metadata.
For the 8.6 Apple menu, appleMenuProfile: macos-8.6 invokes a host-only
title-to-file-identity join, and the renderer loads native ics8 art from the
offline pack. Neither list metadata nor menu icon identity is encoded back into
the guest scene, and no framebuffer pixels are used as production menu rows.