Releases: minigame-labs/migo-runtime
Release list
v0.9.10
Runtime SDKs for Android, Linux, OpenHarmony, Windows, iOS and macOS.
What changed in v0.9.10
Fixed
- Android C ABI: JPEG images still failed to decode after v0.9.9's fix -- Skia,
as this engine links it, decodes PNG but not JPEG, and the Java SDK's
BitmapFactory fallback is out of a C host's reach. The C ABI package now
carries the Rust image decoders every other platform's C ABI already has; the
Java SDK's AAR is unchanged. Found by migo-conformance's new image-decode
bundle, which runs every decode path on both Android embeddings.
Assets
| File | Size | What it is |
|---|---|---|
migo-0.9.10-android-nojni.aar |
0 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.10-android.aar |
33 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.10-apple-sdk.zip |
330 MB | Swift package for iOS (Performance+) and macOS (V8) -- unzip and add as a local package |
migo-0.9.10-capi-android-arm64.tar.gz |
79 MB | C ABI SDK for android-arm64 -- headers, library, CMake package |
migo-0.9.10-capi-android-x86_64.tar.gz |
76 MB | C ABI SDK for android-x86_64 -- headers, library, CMake package |
migo-0.9.10-capi-linux-arm64.tar.gz |
69 MB | C ABI SDK for linux-arm64 -- headers, library, CMake package |
migo-0.9.10-capi-linux-x86_64.tar.gz |
69 MB | C ABI SDK for linux-x86_64 -- headers, library, CMake package |
migo-0.9.10-capi-ohos-arm64.tar.gz |
90 MB | C ABI SDK for ohos-arm64 -- headers, library, CMake package |
migo-0.9.10-capi-ohos-x86_64.tar.gz |
88 MB | C ABI SDK for ohos-x86_64 -- headers, library, CMake package |
migo-0.9.10-capi-windows-arm64.tar.gz |
27 MB | C ABI SDK for windows-arm64 -- headers, library, CMake package |
migo-0.9.10-capi-windows-x86_64.tar.gz |
27 MB | C ABI SDK for windows-x86_64 -- headers, library, CMake package |
migo-0.9.10-jni-android-arm64.tar.gz |
16 MB | see the file |
migo-0.9.10-jni-android-x86_64.tar.gz |
16 MB | see the file |
One AAR is published, carrying both ABIs. A shipped app carries one: add
ndk { abiFilters 'arm64-v8a' } to your defaultConfig and the packaged native
library is about half the size, or publish an App Bundle and Play delivers per device.
There is no separate slim build -- the product profile is an internal build axis, not
a choice an integrator can make.
iOS and macOS
migo-0.9.10-apple-sdk.zip unpacks to MigoApple/, a Swift package: add it in
Xcode with File > Add Package Dependencies > Add Local, then link
MigoApplePerformancePlus (iOS) or MigoMacV8 (macOS) and put a MigoGameView
on screen. MigoApple/README.md has the integration steps. On macOS the app must be
signed with the hardened runtime and com.apple.security.cs.allow-jit, and embed ANGLE
with Frameworks/Scripts/embed-apple-angle.sh; the view refuses to start without the
entitlement and says why.
Verifying a download
SHA256SUMS.txt covers every asset built by CI:
sha256sum -c SHA256SUMS.txt 2>/dev/null | grep -v ': OK$' || echo "all verified"Every runtime payload also has an artifact-bound <asset>.sbom.cdx.json recording
its SHA-256, exact target/profile and reachable dependency graph. Provenance
<asset>.attestation.json sidecars record package identity; the checksum manifest
covers both payloads and sidecars. scripts/verify-release-assets.sh <tag> checks
that no published asset escapes that coverage.
Source code (zip / tar.gz)
Those two links are generated by GitHub from the tag and cannot be removed. Do not
build from them. They omit submodules and Git LFS content, so a build from the zip
fails in ways that look like repository corruption. Clone the tag instead:
git clone --branch v0.9.10 --recurse-submodules https://github.com/minigame-labs/migo.gitIntegration
See migo-examples for runnable
samples, and BUILD.md
for building from source.
What's Changed
- The C ABI freeze list records Android performance as done, measured against the Java SDK on the published v0.9.9 by @jmcgill-d in #313
- The Android C ABI carries the Rust image decoders, so a C host decodes JPEG as well as PNG by @jmcgill-d in #314
- Release v0.9.10 by @jmcgill-d in #315
Full Changelog: v0.9.9...v0.9.10
v0.9.9
Runtime SDKs for Android, Linux, OpenHarmony, Windows, iOS and macOS.
What changed in v0.9.9
Fixed
- Android C ABI: images never decoded, so every WebGL game rendered black while
its frame loop kept running. Android builds leave the Rust image decoders out
and rely on the Java SDK to register BitmapFactory at load time, which a C
host never runs. The C ABI now registers Skia's decoders -- straight into an
AHardwareBufferwhere the renderer can import one, into RGBA memory where an
image must be CPU-backed -- at no binary cost, since Skia is already linked
for rendering. The Android C host's on-device check now paints its first frame
from decoded images on both paths, so this cannot pass unnoticed again.
Assets
| File | Size | What it is |
|---|---|---|
migo-0.9.9-android-nojni.aar |
0 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.9-android.aar |
33 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.9-apple-sdk.zip |
330 MB | Swift package for iOS (Performance+) and macOS (V8) -- unzip and add as a local package |
migo-0.9.9-capi-android-arm64.tar.gz |
73 MB | C ABI SDK for android-arm64 -- headers, library, CMake package |
migo-0.9.9-capi-android-x86_64.tar.gz |
71 MB | C ABI SDK for android-x86_64 -- headers, library, CMake package |
migo-0.9.9-capi-linux-arm64.tar.gz |
69 MB | C ABI SDK for linux-arm64 -- headers, library, CMake package |
migo-0.9.9-capi-linux-x86_64.tar.gz |
69 MB | C ABI SDK for linux-x86_64 -- headers, library, CMake package |
migo-0.9.9-capi-ohos-arm64.tar.gz |
90 MB | C ABI SDK for ohos-arm64 -- headers, library, CMake package |
migo-0.9.9-capi-ohos-x86_64.tar.gz |
88 MB | C ABI SDK for ohos-x86_64 -- headers, library, CMake package |
migo-0.9.9-capi-windows-arm64.tar.gz |
27 MB | C ABI SDK for windows-arm64 -- headers, library, CMake package |
migo-0.9.9-capi-windows-x86_64.tar.gz |
27 MB | C ABI SDK for windows-x86_64 -- headers, library, CMake package |
migo-0.9.9-jni-android-arm64.tar.gz |
16 MB | see the file |
migo-0.9.9-jni-android-x86_64.tar.gz |
16 MB | see the file |
One AAR is published, carrying both ABIs. A shipped app carries one: add
ndk { abiFilters 'arm64-v8a' } to your defaultConfig and the packaged native
library is about half the size, or publish an App Bundle and Play delivers per device.
There is no separate slim build -- the product profile is an internal build axis, not
a choice an integrator can make.
iOS and macOS
migo-0.9.9-apple-sdk.zip unpacks to MigoApple/, a Swift package: add it in
Xcode with File > Add Package Dependencies > Add Local, then link
MigoApplePerformancePlus (iOS) or MigoMacV8 (macOS) and put a MigoGameView
on screen. MigoApple/README.md has the integration steps. On macOS the app must be
signed with the hardened runtime and com.apple.security.cs.allow-jit, and embed ANGLE
with Frameworks/Scripts/embed-apple-angle.sh; the view refuses to start without the
entitlement and says why.
Verifying a download
SHA256SUMS.txt covers every asset built by CI:
sha256sum -c SHA256SUMS.txt 2>/dev/null | grep -v ': OK$' || echo "all verified"Every runtime payload also has an artifact-bound <asset>.sbom.cdx.json recording
its SHA-256, exact target/profile and reachable dependency graph. Provenance
<asset>.attestation.json sidecars record package identity; the checksum manifest
covers both payloads and sidecars. scripts/verify-release-assets.sh <tag> checks
that no published asset escapes that coverage.
Source code (zip / tar.gz)
Those two links are generated by GitHub from the tag and cannot be removed. Do not
build from them. They omit submodules and Git LFS content, so a build from the zip
fails in ways that look like repository corruption. Clone the tag instead:
git clone --branch v0.9.9 --recurse-submodules https://github.com/minigame-labs/migo.gitIntegration
See migo-examples for runnable
samples, and BUILD.md
for building from source.
What's Changed
- Multi-pointer delivery through the Android C ABI runs on a device, driven by an instrumentation APK and read back as pixels by @jmcgill-d in #307
- The C ABI freeze blockers say what is done: Android packaging ships and runs at API 26, 31 and 34, and every target gates its exports by @jmcgill-d in #308
- The Android C host can link a published C ABI package through find_package, so the released bytes run on a device by @jmcgill-d in #309
- The Android C host takes MIGO_CAPI_LOG from a files/log-level setting, so a benchmark can match the Java SDK's WARN console without a rebuild by @jmcgill-d in #310
- The Android C ABI registers Skia's image decoders, so a C host's games decode images instead of rendering black by @jmcgill-d in #311
- Release v0.9.9 by @jmcgill-d in #312
Full Changelog: v0.9.8...v0.9.9
v0.9.8
Runtime SDKs for Android, Linux, OpenHarmony, Windows, iOS and macOS.
What changed in v0.9.8
Changed
- The engine now waits up to ten seconds, not two, for the renderer to report
its GPU capabilities before it runs content. The wait only guards against a
renderer that has stopped: one that fails reports it immediately, and one
that comes up is waited for only as long as it takes. At two seconds, a
slow but healthy first start -- a cold driver shader cache on a low-end
device, or software rendering on a loaded machine -- was treated as a failure
and the session refused its content for good.
Fixed
- Windows: no session could start when a host's files, cache or code-cache
directory was spelled with.., such as the examples'<files>\..\cache
("initialize sandbox filesystem: failed to pin sandbox roots"). Sandbox roots
were opened in Win32's\\?\namespace, where..is a file name rather
than a step up. A root is now given the meaning Win32 itself gives it before
it is opened; relative roots are still refused. The Windows arms of the
sandbox filesystem and the atomic writer now run in CI, which they had never
done. - Linux: the offscreen (headless) target needed a window server after all.
It asked EGL for the default display, which Mesa resolves to X11, so it
failed to start whereverDISPLAYwas unset -- every CI runner and server.
It now uses Mesa's surfaceless platform when the driver offers it, and asks
only for the pbuffer configuration a headless target renders into. - iOS and macOS: after
migo_surface_release_queryreported RELEASED, the
host'sCAMetalLayercould still be alive for a moment, released on the
render thread just afterwards. Objects ANGLE autoreleased during the surface
teardown lived until the render loop's pool drained, which came after
RELEASED. Teardown now drains them before RELEASED is published, on detach,
on surface replacement and at shutdown.
Assets
| File | Size | What it is |
|---|---|---|
migo-0.9.8-android-nojni.aar |
0 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.8-android.aar |
33 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.8-apple-sdk.zip |
330 MB | Swift package for iOS (Performance+) and macOS (V8) -- unzip and add as a local package |
migo-0.9.8-capi-android-arm64.tar.gz |
73 MB | C ABI SDK for android-arm64 -- headers, library, CMake package |
migo-0.9.8-capi-android-x86_64.tar.gz |
71 MB | C ABI SDK for android-x86_64 -- headers, library, CMake package |
migo-0.9.8-capi-linux-arm64.tar.gz |
69 MB | C ABI SDK for linux-arm64 -- headers, library, CMake package |
migo-0.9.8-capi-linux-x86_64.tar.gz |
69 MB | C ABI SDK for linux-x86_64 -- headers, library, CMake package |
migo-0.9.8-capi-ohos-arm64.tar.gz |
90 MB | C ABI SDK for ohos-arm64 -- headers, library, CMake package |
migo-0.9.8-capi-ohos-x86_64.tar.gz |
88 MB | C ABI SDK for ohos-x86_64 -- headers, library, CMake package |
migo-0.9.8-capi-windows-arm64.tar.gz |
27 MB | C ABI SDK for windows-arm64 -- headers, library, CMake package |
migo-0.9.8-capi-windows-x86_64.tar.gz |
27 MB | C ABI SDK for windows-x86_64 -- headers, library, CMake package |
migo-0.9.8-jni-android-arm64.tar.gz |
16 MB | see the file |
migo-0.9.8-jni-android-x86_64.tar.gz |
16 MB | see the file |
One AAR is published, carrying both ABIs. A shipped app carries one: add
ndk { abiFilters 'arm64-v8a' } to your defaultConfig and the packaged native
library is about half the size, or publish an App Bundle and Play delivers per device.
There is no separate slim build -- the product profile is an internal build axis, not
a choice an integrator can make.
iOS and macOS
migo-0.9.8-apple-sdk.zip unpacks to MigoApple/, a Swift package: add it in
Xcode with File > Add Package Dependencies > Add Local, then link
MigoApplePerformancePlus (iOS) or MigoMacV8 (macOS) and put a MigoGameView
on screen. MigoApple/README.md has the integration steps. On macOS the app must be
signed with the hardened runtime and com.apple.security.cs.allow-jit, and embed ANGLE
with Frameworks/Scripts/embed-apple-angle.sh; the view refuses to start without the
entitlement and says why.
Verifying a download
SHA256SUMS.txt covers every asset built by CI:
sha256sum -c SHA256SUMS.txt 2>/dev/null | grep -v ': OK$' || echo "all verified"Every runtime payload also has an artifact-bound <asset>.sbom.cdx.json recording
its SHA-256, exact target/profile and reachable dependency graph. Provenance
<asset>.attestation.json sidecars record package identity; the checksum manifest
covers both payloads and sidecars. scripts/verify-release-assets.sh <tag> checks
that no published asset escapes that coverage.
Source code (zip / tar.gz)
Those two links are generated by GitHub from the tag and cannot be removed. Do not
build from them. They omit submodules and Git LFS content, so a build from the zip
fails in ways that look like repository corruption. Clone the tag instead:
git clone --branch v0.9.8 --recurse-submodules https://github.com/minigame-labs/migo.gitIntegration
See migo-examples for runnable
samples, and BUILD.md
for building from source.
What's Changed
- Headless Linux renders on Mesa's surfaceless display, so the presentation gate paints on a runner with no window server by @jmcgill-d in #298
- read_file's path counters are per thread, so a test sees only its own reads by @jmcgill-d in #300
- Windows VFS roots take the meaning Win32 gives them before going verbatim, so a root spelled with .. pins by @jmcgill-d in #299
- Surface teardown drains its autoreleased objects before the lease drops, so RELEASED follows the layer's last release by @jmcgill-d in #301
- 0.9.7 docs by @jmcgill-d in #302
- The GPU readiness deadline is a watchdog for a stuck renderer, so it is ten seconds rather than two by @jmcgill-d in #303
- The docs smoke test asserts the English 0.9 archive is translated and stays in its version, now that it is no longer a stub by @jmcgill-d in #304
- The hygiene gate exempts the English 0.9 concept page like its three siblings, and matches the hostname as a token by @jmcgill-d in #305
- Release v0.9.8: Windows sessions start again, headless Linux needs no window server, Change Date 2030-09-26, all eight snapshots regenerated by @jmcgill-d in #306
Full Changelog: v0.9.7...v0.9.8
v0.9.7
Runtime SDKs for Android, Linux, OpenHarmony, Windows, iOS and macOS.
What changed in v0.9.7
Added
-
iOS and macOS SDK:
migo-<version>-apple-sdk.zip, a Swift package with one
view per platform.MigoGameView(MigoApplePerformancePluson iOS,
MigoMacV8on macOS) takes an installed game and owns everything between it
and pixels -- engine session,CAMetalLayer, display clock, input, app
lifecycle, and on iOS the audio session and WebContent crash recovery.
MigoGameInstallerinstalls a package atomically and skips a version that is
already there. Both products ship a privacy manifest that a gate checks
against the engine's sources in both directions. The zip carries its own
.attestation.json, like every other SDK archive, and a runnable app for
each platform isapple-swift/in migo-examples. -
iOS and macOS: the soft keyboard.
migo.showKeyboardopens the system
keyboard on iOS and a text field along the game's bottom edge on macOS; the
player's text comes back asonKeyboardInput/Confirm/Complete. On the
iOS lane the three keyboard ops became host commands to the same keyboard
service the embedded runtime calls, so a host with no keyboard refuses them
in the same words. -
iOS and macOS: the device.
vibrateShort/vibrateLongdrive the Taptic
Engine on iOS;setKeepScreenOnholds the display awake (the idle timer on
iOS, a power assertion on macOS) and gives it back when the game ends;
getGameLogManager().logentries reach the app asMigoGameView.Event.gameLog;
getNetworkType/onNetworkStatusChangefollowNWPathMonitor, and on iOS
getBatteryInforeports the battery and Low Power Mode. A Mac has nothing to
vibrate and no battery API a game should need, so those answer "not
supported" there, as on a device without the hardware. -
C ABI: device capabilities. Three optional, independent callbacks appended to
MigoHostCallbacks--on_vibrate,on_keep_screen_on,on_game_log-- each
offered to content exactly when installed, and two report functions,
migo_session_set_network_statusandmigo_session_set_battery_status,
whose last report the engine answers content's synchronous reads from and
forwards a network change while content listens. An older host is
zero-extended to none of them, which is the previous behaviour. -
C ABI:
MigoEngineConfig.code_signing_public_key, the Ed25519 key signed
content is verified against. Until now a C ABI host could not supply one, so
its only configuration that loaded content was
MIGO_ENGINE_FLAG_ALLOW_UNSIGNED_CONTENT. Appended to the record: a host
passing the old size is zero-extended to "no key" and behaves as before.
Setting the key and the unsigned flag together is refused. -
macOS: a host-owned
CAMetalLayercan be attached through the C ABI.
MIGO_PLATFORM_MACOS_CA_METAL_LAYERnow appears in the library's advertised
attachable kinds, and it appears because an attach ran, not because a backend
compiled:apple-sdk.ymlrun 34017950809 built a realCAMetalLayeron a
hosted macOS runner, carried it throughmigo_session_attach_surfaceas
generation 1, and completed the whole retirement handshake —begin_detach,
poll toMIGO_SURFACE_RELEASE_RELEASED,migo_surface_release_destroy,
migo_session_destroy,migo_engine_destroy— before releasing the layer.
AnNSViewis refused rather than resolved to a layer, so the host keeps
ownership of its own drawable. The same run loaded the pinned ANGLE under the
name the recipe declares and got an EGL display back from it. iOS is
deliberately not included: its arm of the same module compiles everywhere and
has attached nothing anywhere. -
Images with transparency are transcoded to ASTC at package ingest on devices
whose GPU decodes it, and stay on ETC2 elsewhere. The block footprint is
chosen per image by what it reconstructs: the encoder grades its own output
against the source and takes the largest block whose worst channel error stays
within budget, so a smooth image lands at 0.25 bytes per pixel — a quarter of
ETC2 RGBA, at higher fidelity — while a sprite with a hard alpha edge stays at
one byte per pixel rather than losing the edge. Opaque images stay on ETC2 RGB
everywhere, because at half a byte per pixel it is smaller than any ASTC
footprint. The choice is per image and per device, which is only possible
because ingest runs on the device. -
Apple: ANGLE over Metal is built from source and pinned
(contracts/artifact-manifest/apple-angle.lock.json+
scripts/build-angle-apple.sh+scripts/fetch-apple-angle.sh) for every
slice group the engine is built for — iOS, the iOS simulator and macOS, five
configurations in all. ANGLE publishes no official prebuilt binaries for any
platform, so these are self-hosted and hash-verified before use, the same
model the Windows ANGLE runtime and the V8 archives already follow. It exists
because there is no GL framework on iOS: rustc's own link line for the Apple
slices asks for-framework OpenGLon macOS and for no GL framework at all on
iOS, while Skia is configured for its GL backend. Metal is the only backend
built; the desktop GL backend, which defaults to on for macOS, is pinned off
so both Apple platforms resolve the same renderer. The published shape differs
per platform because upstream's does — a framework bundle on iOS, where Apple
accepts an embedded framework and rejects a bare dylib, and a shared library
on macOS — and it is deliberately not evened out:libEGLlocateslibGLESv2
at run time by composing a name against a directory, and that name and
directory are platform-specific, so repackaging either side would leave ANGLE
searching where its dispatch library is not.--print-loader-layoutis what
answers where each library has to sit; the xcframeworks are split along the
same axis instead, one per library per platform family. -
The runtime reports how many times a frame's drawing crossed from JavaScript
into native, on a five-second window, atinfolevel:[boundary] frames=… crossings/frame=… worst=… commands/frame=… commands/crossing=…. The ratio is
what a command stream exists to move, and it was previously only observable
from inside a test.
Changed
- Canvas2D commands cross the JavaScript/native boundary as a binary command
stream rather than one op per call, sharing one buffer and one opcode space
with the WebGL stream so the order between the two survives the crossing
without a barrier between every pair of commands. A frame that issues three
hundred 2D calls now crosses once.
Fixed
-
iOS: installing an update over a game that had already run under code
signing failed on an iPhone ("you don't have permission"): Darwin refuses to
rename a directory its caller cannot write, and the engine seals the package
read-only. The installer now restores the owner's write bit on the sealed
root alone before moving it aside. The simulator's host file system had let
the rename through. -
iOS and macOS: the engine's render and session threads ran at the default
quality-of-service class, so the main thread waiting on the session's
startup was a priority inversion and the frame work could land on efficiency
cores. They are now user-interactive; decode and IO threads are utility. -
iOS: signed content was not verified. The Performance+ lane mounted the
installed package as it was, so a host that configured a signing key got no
verification at all. It now runs the embedded execution's launch sequence
before anything is mounted -- sealed-receipt check, else a full manifest and
file-hash verification that seals the tree -- and refuses the load on any
mismatch, or on signing with no key. -
MigoGameInstallercould not update a game that had run under signing: the
engine seals a verified tree read-only, and the installer replaced it in
place. An update now renames the sealed tree aside, renames the new one in
and removes the old one as a trusted uninstall. -
iOS: a game that called
migo.exitMiniProgram()stopped drawing and the app
was never told. The external-frame session ended on the request without the
exit notification the in-process runtime sends; it now sends it. -
The Apple SDK could not put its iOS and macOS engines in one xcframework:
assembly required byte-identical header directories, and each group's module
map lists the frameworks its own archive links. Only the C headers are now
compared across groups. -
SBOMs listed crates the shipped build never compiles.
cargo metadata
resolves features for the whole workspace, so a crate any member enabled was
in every artifact's graph -- the iOS SDK, built without a JavaScript engine,
would have listedv8. Each SBOM is now cut to its build's owncargo tree. -
ASTC 8x8 textures upload again. The engine mapped
VK_FORMAT_ASTC_8x8_UNORM_BLOCKto0x93B9, which is the token for a 10x6
block, soglCompressedTexImage2Drejected every such texture with
GL_INVALID_VALUE. It had never fired because nothing produced ASTC 8x8, and
the test that covered it asserted the same wrong number, copied from the
constant it was checking; the tokens are now derived from the block size the
extension assigns them to. -
ctx.fillStyle = "transparent"no longer paints opaque black. The keyword was
missing from the engine's named-colour table, so it fell through to the
unknown-name branch, which reads black -- the loudest possible wrong answer for
a keyword whose meaning is "do not paint".strokeStyleandshadowColorhad
the same fault, and gradient colour stops were unaffected.
Assets
| File | Size | What it is |
|---|---|---|
migo-0.9.7-android-nojni.aar |
0 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.7-android.aar |
33 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
| `mig... |
v0.9.7-rc.2
Runtime SDKs for Android, Linux, OpenHarmony, Windows, iOS and macOS.
What changed in v0.9.7-rc.2
Added
-
iOS and macOS SDK:
migo-<version>-apple-sdk.zip, a Swift package with one
view per platform.MigoGameView(MigoApplePerformancePluson iOS,
MigoMacV8on macOS) takes an installed game and owns everything between it
and pixels -- engine session,CAMetalLayer, display clock, input, app
lifecycle, and on iOS the audio session and WebContent crash recovery.
MigoGameInstallerinstalls a package atomically and skips a version that is
already there. Both products ship a privacy manifest that a gate checks
against the engine's sources in both directions. -
iOS and macOS: the soft keyboard.
migo.showKeyboardopens the system
keyboard on iOS and a text field along the game's bottom edge on macOS; the
player's text comes back asonKeyboardInput/Confirm/Complete. On the
iOS lane the three keyboard ops became host commands to the same keyboard
service the embedded runtime calls, so a host with no keyboard refuses them
in the same words. -
iOS and macOS: the device.
vibrateShort/vibrateLongdrive the Taptic
Engine on iOS;setKeepScreenOnholds the display awake (the idle timer on
iOS, a power assertion on macOS) and gives it back when the game ends;
getGameLogManager().logentries reach the app asMigoGameView.Event.gameLog;
getNetworkType/onNetworkStatusChangefollowNWPathMonitor, and on iOS
getBatteryInforeports the battery and Low Power Mode. A Mac has nothing to
vibrate and no battery API a game should need, so those answer "not
supported" there, as on a device without the hardware. -
C ABI: device capabilities. Three optional, independent callbacks appended to
MigoHostCallbacks--on_vibrate,on_keep_screen_on,on_game_log-- each
offered to content exactly when installed, and two report functions,
migo_session_set_network_statusandmigo_session_set_battery_status,
whose last report the engine answers content's synchronous reads from and
forwards a network change while content listens. An older host is
zero-extended to none of them, which is the previous behaviour. -
C ABI:
MigoEngineConfig.code_signing_public_key, the Ed25519 key signed
content is verified against. Until now a C ABI host could not supply one, so
its only configuration that loaded content was
MIGO_ENGINE_FLAG_ALLOW_UNSIGNED_CONTENT. Appended to the record: a host
passing the old size is zero-extended to "no key" and behaves as before.
Setting the key and the unsigned flag together is refused. -
macOS: a host-owned
CAMetalLayercan be attached through the C ABI.
MIGO_PLATFORM_MACOS_CA_METAL_LAYERnow appears in the library's advertised
attachable kinds, and it appears because an attach ran, not because a backend
compiled:apple-sdk.ymlrun 34017950809 built a realCAMetalLayeron a
hosted macOS runner, carried it throughmigo_session_attach_surfaceas
generation 1, and completed the whole retirement handshake —begin_detach,
poll toMIGO_SURFACE_RELEASE_RELEASED,migo_surface_release_destroy,
migo_session_destroy,migo_engine_destroy— before releasing the layer.
AnNSViewis refused rather than resolved to a layer, so the host keeps
ownership of its own drawable. The same run loaded the pinned ANGLE under the
name the recipe declares and got an EGL display back from it. iOS is
deliberately not included: its arm of the same module compiles everywhere and
has attached nothing anywhere. -
Images with transparency are transcoded to ASTC at package ingest on devices
whose GPU decodes it, and stay on ETC2 elsewhere. The block footprint is
chosen per image by what it reconstructs: the encoder grades its own output
against the source and takes the largest block whose worst channel error stays
within budget, so a smooth image lands at 0.25 bytes per pixel — a quarter of
ETC2 RGBA, at higher fidelity — while a sprite with a hard alpha edge stays at
one byte per pixel rather than losing the edge. Opaque images stay on ETC2 RGB
everywhere, because at half a byte per pixel it is smaller than any ASTC
footprint. The choice is per image and per device, which is only possible
because ingest runs on the device. -
Apple: ANGLE over Metal is built from source and pinned
(contracts/artifact-manifest/apple-angle.lock.json+
scripts/build-angle-apple.sh+scripts/fetch-apple-angle.sh) for every
slice group the engine is built for — iOS, the iOS simulator and macOS, five
configurations in all. ANGLE publishes no official prebuilt binaries for any
platform, so these are self-hosted and hash-verified before use, the same
model the Windows ANGLE runtime and the V8 archives already follow. It exists
because there is no GL framework on iOS: rustc's own link line for the Apple
slices asks for-framework OpenGLon macOS and for no GL framework at all on
iOS, while Skia is configured for its GL backend. Metal is the only backend
built; the desktop GL backend, which defaults to on for macOS, is pinned off
so both Apple platforms resolve the same renderer. The published shape differs
per platform because upstream's does — a framework bundle on iOS, where Apple
accepts an embedded framework and rejects a bare dylib, and a shared library
on macOS — and it is deliberately not evened out:libEGLlocateslibGLESv2
at run time by composing a name against a directory, and that name and
directory are platform-specific, so repackaging either side would leave ANGLE
searching where its dispatch library is not.--print-loader-layoutis what
answers where each library has to sit; the xcframeworks are split along the
same axis instead, one per library per platform family. -
The runtime reports how many times a frame's drawing crossed from JavaScript
into native, on a five-second window, atinfolevel:[boundary] frames=… crossings/frame=… worst=… commands/frame=… commands/crossing=…. The ratio is
what a command stream exists to move, and it was previously only observable
from inside a test.
Changed
- Canvas2D commands cross the JavaScript/native boundary as a binary command
stream rather than one op per call, sharing one buffer and one opcode space
with the WebGL stream so the order between the two survives the crossing
without a barrier between every pair of commands. A frame that issues three
hundred 2D calls now crosses once.
Fixed
-
iOS: installing an update over a game that had already run under code
signing failed on an iPhone ("you don't have permission"): Darwin refuses to
rename a directory its caller cannot write, and the engine seals the package
read-only. The installer now restores the owner's write bit on the sealed
root alone before moving it aside. The simulator's host file system had let
the rename through. -
iOS and macOS: the engine's render and session threads ran at the default
quality-of-service class, so the main thread waiting on the session's
startup was a priority inversion and the frame work could land on efficiency
cores. They are now user-interactive; decode and IO threads are utility. -
iOS: signed content was not verified. The Performance+ lane mounted the
installed package as it was, so a host that configured a signing key got no
verification at all. It now runs the embedded execution's launch sequence
before anything is mounted -- sealed-receipt check, else a full manifest and
file-hash verification that seals the tree -- and refuses the load on any
mismatch, or on signing with no key. -
MigoGameInstallercould not update a game that had run under signing: the
engine seals a verified tree read-only, and the installer replaced it in
place. An update now renames the sealed tree aside, renames the new one in
and removes the old one as a trusted uninstall. -
iOS: a game that called
migo.exitMiniProgram()stopped drawing and the app
was never told. The external-frame session ended on the request without the
exit notification the in-process runtime sends; it now sends it. -
The Apple SDK could not put its iOS and macOS engines in one xcframework:
assembly required byte-identical header directories, and each group's module
map lists the frameworks its own archive links. Only the C headers are now
compared across groups. -
SBOMs listed crates the shipped build never compiles.
cargo metadata
resolves features for the whole workspace, so a crate any member enabled was
in every artifact's graph -- the iOS SDK, built without a JavaScript engine,
would have listedv8. Each SBOM is now cut to its build's owncargo tree. -
ASTC 8x8 textures upload again. The engine mapped
VK_FORMAT_ASTC_8x8_UNORM_BLOCKto0x93B9, which is the token for a 10x6
block, soglCompressedTexImage2Drejected every such texture with
GL_INVALID_VALUE. It had never fired because nothing produced ASTC 8x8, and
the test that covered it asserted the same wrong number, copied from the
constant it was checking; the tokens are now derived from the block size the
extension assigns them to. -
ctx.fillStyle = "transparent"no longer paints opaque black. The keyword was
missing from the engine's named-colour table, so it fell through to the
unknown-name branch, which reads black -- the loudest possible wrong answer for
a keyword whose meaning is "do not paint".strokeStyleandshadowColorhad
the same fault, and gradient colour stops were unaffected.
Assets
| File | Size | What it is |
|---|---|---|
migo-0.9.7-rc.2-android-nojni.aar |
0 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.7-rc.2-android.aar |
33 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.7-rc.2-apple-sdk.zip |
330 MB | Swift package for iOS (Performance+) and macOS (V8) -- unzip and add as a local package |
| `migo-... |
Skia for Apple, built with skia_gl_standard="" for macOS 11.0
Built by .github/workflows/apple-skia-binaries.yml from scripts/package-apple-skia-binaries.sh. These are skia-bindings archives for the darwin triples, built from source with the GN argument that lets Skia accept an ANGLE ES context (see scripts/apple-skia-gl-env.sh for why the stock ones cannot) and against the macOS 11.0 deployment floor in contracts/apple/deployment-floor.json.
Skia for Apple, built with skia_gl_standard=""
Built by .github/workflows/apple-skia-binaries.yml from scripts/package-apple-skia-binaries.sh. These are skia-bindings archives for the darwin triples, built from source with the GN argument that lets Skia accept an ANGLE ES context; see scripts/apple-skia-gl-env.sh for why the stock ones cannot.
ANGLE for Apple (52f59428)
ANGLE built from source for every Apple slice group migo's engine is built for.
These are dependency artifacts, not a product release. They exist because ANGLE
(google/angle) publishes no official prebuilt binaries for any platform, so there
is no upstream URL to pin — only a self-built, fully-provenanced archive, the same
position contracts/artifact-manifest/windows-angle.lock.json records for Windows.
Why the engine needs them: there is no GL framework on iOS. rustc's own link line
for each Apple slice says so — macOS answers -framework OpenGL and iOS answers
nothing — and Skia is configured for its GL backend. ANGLE over Metal fills that
gap.
| ANGLE revision | 52f594287836c9970b67920da0633077aee42649 |
| Built by | .github/workflows/apple-angle.yml on macos-15 |
| Pinned by | contracts/artifact-manifest/apple-angle.lock.json |
| Fetched by | scripts/fetch-apple-angle.sh (checks sha256 before use) |
Backends: Metal only. angle_enable_gl=false is deliberate rather than default —
GL defaults to on for macOS, and leaving it on would let EGL_DEFAULT_DISPLAY
resolve macOS to a desktop GL backend while iOS resolved to Metal, so the two Apple
platforms would not be running the same renderer. Vulkan, WebGPU and the null
backend are off.
Shapes differ by platform because upstream's own template does: angle_shared_library
switches to ios_framework_bundle when is_ios, so iOS ships framework bundles and
macOS ships shared libraries. That is the right split — Apple accepts an embedded
framework in an app and rejects a bare dylib.
How they have to be laid out, which is not a matter of taste. libEGL does not
link libGLESv2 — otool -L names only system frameworks — it opens it at run time
under a name it composes against a directory (libEGL_autogen.cpp:41,
system_utils.cpp:221-234, system_utils_{ios,mac,posix}.cpp at this revision):
| where ANGLE looks for its dispatch library | |
|---|---|
| macOS | <the directory libEGL was loaded from>/libGLESv2.dylib |
| iOS / iOS simulator | <the app bundle>/Frameworks/libGLESv2.framework/libGLESv2 |
So the two products go side by side, unpacked as they are. Repackaging the macOS
libraries into framework bundles — the obvious way to get one xcframework holding
every platform, since xcodebuild -create-xcframework refuses to mix frameworks and
libraries — moves libEGL into Versions/A/ and the search follows it somewhere
libGLESv2.dylib is not. Nothing fails at build time; the first symptom is an
application that cannot get an EGL display. scripts/build-angle-apple.sh --print-loader-layout <platform> answers this, and the xcframeworks are split by
platform family instead: one per library per family.
Not byte-reproducible: gzip stores an mtime. The pin is trust-on-first-use — the
hash proves the bytes have not changed since they were hashed.
v0.9.6
Runtime SDKs for Android, Linux, OpenHarmony and Windows.
What changed in v0.9.6
A hotfix for a WebGL regression in v0.9.5.
Fixed
bufferData(target, ArrayBuffer, usage)uploads its data again. v0.9.5 added
asize < 0guard tobufferDatathat ran before the code checked whether a
payload was supplied, and the JS binding passessize = -1on the data path
because the field is unused there. So the most common WebGL upload -- vertex
and index buffers -- became a silent no-op that also recorded a spurious
INVALID_VALUE, and every WebGL draw painted black. The op had no test;
migo-conformance'swebgl-basicsbundle caught it on the first run against
the v0.9.5 release AAR. The negative-size check now runs only on the
size-only form, andop_buffer_datahas a regression test on both forms.
Assets
| File | Size | What it is |
|---|---|---|
migo-0.9.6-android-nojni.aar |
0 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.6-android.aar |
32 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.6-capi-android-arm64.tar.gz |
72 MB | C ABI SDK for android-arm64 -- headers, library, CMake package |
migo-0.9.6-capi-android-x86_64.tar.gz |
69 MB | C ABI SDK for android-x86_64 -- headers, library, CMake package |
migo-0.9.6-capi-linux-arm64.tar.gz |
69 MB | C ABI SDK for linux-arm64 -- headers, library, CMake package |
migo-0.9.6-capi-linux-x86_64.tar.gz |
69 MB | C ABI SDK for linux-x86_64 -- headers, library, CMake package |
migo-0.9.6-capi-ohos-arm64.tar.gz |
88 MB | C ABI SDK for ohos-arm64 -- headers, library, CMake package |
migo-0.9.6-capi-ohos-x86_64.tar.gz |
87 MB | C ABI SDK for ohos-x86_64 -- headers, library, CMake package |
migo-0.9.6-capi-windows-arm64.tar.gz |
27 MB | C ABI SDK for windows-arm64 -- headers, library, CMake package |
migo-0.9.6-capi-windows-x86_64.tar.gz |
27 MB | C ABI SDK for windows-x86_64 -- headers, library, CMake package |
migo-0.9.6-jni-android-arm64.tar.gz |
16 MB | see the file |
migo-0.9.6-jni-android-x86_64.tar.gz |
16 MB | see the file |
One AAR is published, carrying both ABIs. A shipped app carries one: add
ndk { abiFilters 'arm64-v8a' } to your defaultConfig and the packaged native
library is about half the size, or publish an App Bundle and Play delivers per device.
There is no separate slim build -- the product profile is an internal build axis, not
a choice an integrator can make.
Verifying a download
SHA256SUMS.txt covers every asset built by CI:
sha256sum -c SHA256SUMS.txt 2>/dev/null | grep -v ': OK$' || echo "all verified"Every runtime payload also has an artifact-bound <asset>.sbom.cdx.json recording
its SHA-256, exact target/profile and reachable dependency graph. Provenance
<asset>.attestation.json sidecars record package identity; the checksum manifest
covers both payloads and sidecars. scripts/verify-release-assets.sh <tag> checks
that no published asset escapes that coverage.
Source code (zip / tar.gz)
Those two links are generated by GitHub from the tag and cannot be removed. Do not
build from them. They omit submodules and Git LFS content, so a build from the zip
fails in ways that look like repository corruption. Clone the tag instead:
git clone --branch v0.9.6 --recurse-submodules https://github.com/minigame-labs/migo.gitIntegration
See migo-examples for runnable
samples, and BUILD.md
for building from source.
What's Changed
- Restore bufferData(target, ArrayBuffer, usage) — the size guard was dropping every WebGL upload by @jmcgill-d in #171
Full Changelog: v0.9.5...v0.9.6
v0.9.5
Warning
WebGL is broken in this release. bufferData(target, ArrayBuffer, usage) — the
vertex/index-buffer upload every WebGL app makes — is a silent no-op here, so WebGL
content renders black. Canvas2D is unaffected. Fixed in v0.9.6; use that instead.
Runtime SDKs for Android, Linux, OpenHarmony and Windows.
What changed in v0.9.5
A correctness and efficiency pass over io, graphics and audio. Redundant GL
calls that slipped past the state shadow are deduplicated and the shadow itself
stopped hashing to decide; measureText and per-sample AudioParam automation
stop allocating and re-walking on every call; AnalyserNode's scalar
parameters take effect; compressImage shares a two-thread pool instead of
spawning one thread per image. A symlink race in sub-package and ZIP extraction
is closed, a WebGL context loss no longer strands a game between frames, and
getUpdateManager() stops inventing updates. Host frame-timestamp jitter no
longer drops frames for the rest of a session, and MigoRuntime reports the
real engine version.
Added
AnalyserNode's scalar parameters take effect.minDecibels,maxDecibels
andsmoothingTimeConstantwere accepted and then ignored, so
getByteFrequencyData/getFloatFrequencyDatareturned unsmoothed data over
a fixed range regardless of what content set. They now reach the audio thread
through a dedicated op and apply.scripts/build-aar.sh --jitlessbuilds an engine that asks V8 to stop
generating machine code (--jitless). HarmonyOS 5.0.0(12) forbids a
third-party VM from making memory executable, so Migo runs interpreted on
NEXT whether it asks to or not; this is the build that produces the size of
that penalty rather than guessing at it. Off by default, selected by no
release path, and the artifact is named so it cannot be mistaken for one. The
measured numbers are inmigo-bench/JITLESS.md.
Changed
migo.compressImageno longer starts one OS thread per call. A batch — a
screenshot sheet, an avatar pipeline — got a thread per image, each with a
1 MB stack, all competing with the render thread. It now uses a shared,
daemon pool of two.PRESCREEN.mdand the prescreen report now say which published names are
no-op stubs and which fail loudly, rather than covering all of them with one
sentence true of only some. The two stubs that were failing silently now
answer"<api>:fail not supported"like the other 86.- On Android,
requestVsyncresolves its Java method ID once per process
instead of hashing the method name and signature against the JNI method cache
on every frame. The lookup is off the frame-scheduling path now; the id is a
process-lifetime constant. - The GitHub Release notes now lead with this file's
## v<version>section.
They previously carried only the asset table, verification steps, and GitHub's
raw commit list — the curated record of what changed was in the repo but not
on the release page.write-release-notes.shrefuses to write notes for a
version whose section is missing or empty. - Redundant GL calls stop reaching the driver on two more paths.
glScissor
could not be deduplicated before, because the dirty-region Canvas2D batcher
and the DrawingBuffer blit both moved the scissor box outside the state
shadow; both now route through it. The texture-unit,glEnable/glDisable
and vertex-attribute shadows were rebuilt from hash maps and sets keyed by GL
enum or(vao, index)into arrays and bitmasks, so deciding that a call is
redundant no longer hashes. AudioParamautomation is evaluated once per 128-sample block instead of once
per sample. A parameter driven bysetValueAtTime, a ramp,setTargetAtTime
orsetValueCurveAtTimewalked its whole event timeline for every sample in
the block; it now makes one forward pass.measureTextand the Canvas2D font ops no longer allocate on every call. They
cloned the whole text string to keep a character count for a render-thread
timeout branch they rarely reach; they now take the count first and move the
original.- WebGL error reporting under queue pressure follows the spec. Past the
per-context cap the queue used to discard the oldest un-retrieved error; it
now keeps the oldest, drops the newest, reserves the last slot for a sticky
OUT_OF_MEMORY, and counts the drops in the render diagnostics. chacha20(pulled in transitively byrand) is updated off a yanked
release. No advisory attached;cargo auditstays clean.
Fixed
- A re-linked WebGL program's uniforms reach the driver again.
glLinkProgram
gives a program fresh, zeroed uniform storage; the per-(program, location)
dedup cache was not cleared across it, so content that re-linked a program and
then re-uploaded an unchanged uniform value had that upload silently dropped —
the draw used the reset value, with no GL error and nothing in a log. - Sub-package and ZIP extraction is no longer open to a symlink race. An entry's
path was validated as a string and then resolved again by a separate syscall;
a path component could turn into a symlink in between and let the entry write
outside its destination directory. Every component is now reached with
openatunderO_NOFOLLOWfrom a held directory descriptor, so a swapped
component fails withELOOPat the moment of use rather than after a passed
check. fetchno longer puts the sandbox's internal path layout in a request to a
remote server.- Terminating a Worker while it is still starting up is no longer a silent
no-op. The handle the host holds was published only after the worker runtime
finished initialising, soterminate()called in that window did nothing and
the worker ran to completion. - A game is no longer stranded after a WebGL context loss and restore. The
render drain dispatcheswebglcontextlost/webglcontextrestoredinto JS;
a handler that resolves a promise or requests a frame leaves a pending task,
and the event loop stayed parked on it until some unrelated host command
arrived — which, for a game between frames, it never did. - The C ABI's minimum
struct_sizeforMigoHostCallbackswas wrong on ILP32.
It used a literal32, correct for LP64's pointer width; a 32-bit host
passing a struct that fully containeddispatch(minimum20there) was
refused withMIGO_ERROR_INVALID_ARGUMENT. The minimum is now derived from
the field offset. migo.getUpdateManager()no longer invents updates. It was deciding with
Math.random() < 0.3at construction whether to fire
onCheckForUpdate({hasUpdate: true}), so roughly a quarter of launches
showed the game's own "new version — restart?" prompt and thenapplyUpdate()
restarted nothing. This runtime has no update channel; the manager now says so
instead of pretending.- Non-Android hosts (Linux, Windows, OpenHarmony, bare C embedders) now remove
a session's sandbox/tmpdirectory when the session ends.GamePaths::clean_temp
had no caller outside Android's Java SDK, so every session left its
tmp/{id}subtree under the cache root for the life of the install. - Host timestamp jitter no longer drops frames for the rest of a session. The
vsync decimator admitted a frame within a fixed 0.25 ms of its deadline, and
every useful cadence puts that deadline exactly on a vsync — so a host whose
frame-callback timestamps jittered by more than that dropped frames
permanently: a replayed 60 Hz stream with 0.4 ms of jitter rendered 14 of 24
vsyncs, a 60 fps request running at ~35 and staying there. The tolerance is
now half the smaller of the last two delivered-vsync gaps, taking the frame
nearest its deadline rather than the first past it. The frame rate the
content asked for now also reaches Android's display-mode hint, so a request
that is a whole divisor of the panel's rate gets an even cadence; and the
requested-rate range and default, which four copies of the rule disagreed on
(2^31and non-finite handled differently on each side, a 24 fps request
silently raised to 30, a ceiling of 120 that left 144 Hz panels unreachable),
now live in one module. MigoRuntime.getNativeVersion()and the publicMigoRuntime.SDK_VERSION
constant report the running engine's version. Both had frozen to an early
release's number while the AAR's ownversionNametrackedrelease/VERSION,
and since they were the same frozen string the SDK's skew check compared a
value against itself and could never fire. The JNIversion()now derives
fromCARGO_PKG_VERSIONandSDK_VERSIONfromBuildInfo.VERSION, both of
whichscripts/test-release-version-contract.shholds equal to
release/VERSION.
Assets
| File | Size | What it is |
|---|---|---|
migo-0.9.5-android-nojni.aar |
0 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.5-android.aar |
32 MB | Android library for Java/Kotlin, arm64-v8a + x86_64 |
migo-0.9.5-capi-android-arm64.tar.gz |
72 MB | C ABI SDK for android-arm64 -- headers, library, CMake package |
migo-0.9.5-capi-android-x86_64.tar.gz |
69 MB | C ABI SDK for android-x86_64 -- headers, library, CMake package |
migo-0.9.5-capi-linux-arm64.tar.gz |
69 MB | C ABI SDK for linux-arm64 -- headers, library, CMake package |
migo-0.9.5-capi-linux-x86_64.tar.gz |
69 MB | C ABI SDK for linux-x86_64 -- headers, library, CMake package |
migo-0.9.5-capi-ohos-arm64.tar.gz |
88 MB | C ABI SDK for ohos-arm64 -- headers, library, CMake package |
migo-0.9.5-capi-ohos-x86_64.tar.gz |
87 MB | C ABI SDK for ohos-x86_64 -- headers, library, CMake package |
migo-0.9.5-capi-windows-arm64.tar.gz |
27 MB | C ABI SDK for windows-arm64 -- headers, library, CMake package |
migo-0.9.5-capi-windows-x86_64.tar.gz |
27 MB | C ABI SDK for windows-x86_64 -- headers, library, CMake package |
| `migo-0.9.5-jni-android-arm64.ta... |