feat(bundle): ship a game as a macOS .app, with a splash it controls
`ludic build` produces a program. Double-clicked it opens a Terminal window, it wears the generic executable icon, it calls itself whatever the file is called, and it carries none of its assets. `ludic bundle` produces an application. Everything it needs is in package.ludic, so the command takes no arguments: an Info.plist and PkgInfo from `app` lines, an .icns built by sips and iconutil at all ten sizes macOS asks for from a single source PNG, the asset pack in Contents/Resources, and an ad-hoc signature - which is not optional on Apple silicon, where an unsigned binary is killed rather than warned about. The bundle identifier falls back to the package path reversed, so a project that never thinks about it still gets a defensible one instead of two apps sharing a key Launch Services hangs the Dock, saved state and permissions off. A bundled game is moved to ~/Library/Application Support/<name> before main, because Finder starts a .app with its working directory at "/" where no save could ever be written. Reads come out of the pack, writes land somewhere real and per-user, and the game's save code needs no change and no platform knowledge. The splash is the other half of looking like an application. A game that loads 165 MB spends a visible moment doing it with nothing on screen, which from the outside is indistinguishable from a launch that failed. splash_show puts a borderless window up from the same constructor that mounts the pack - before main, so it appears while the process is still starting rather than after the slow part it exists to cover - and reads the artwork out of the pack like any other asset. It turns the run loop enough times to be mapped and composited there and then; once composited the backing store survives a busy main thread, so it stays up for the whole load. Nothing hides it automatically. Only the game knows when its first real frame is ready, and a splash that vanishes before that leaves the same black gap it was covering, so the game calls App.splash_hide(). Headless there is no splash and the call lowers to nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ce5bf0fe0e
commit
be74b4de6f
14 changed files with 34667 additions and 33621 deletions
|
|
@ -234,6 +234,23 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
# through a freelist (L_alloc pops @L_freen before growing @L_entc), and component
|
||||
# storage is fixed per-entity arrays — so there is no per-spawn heap allocation or
|
||||
# fragmentation. Pool.* just reads those counters so a game can watch reuse.
|
||||
# App.* — the process itself, as distinct from the window. Today that is the
|
||||
# boot splash: the runtime raises it before main from the pack, and only the
|
||||
# game knows when its first real frame is ready to replace it, so taking it
|
||||
# down is the game's call and never the runtime's.
|
||||
#
|
||||
# Headless there is no splash and no cocoa.ll to hold one, so the call lowers
|
||||
# to nothing rather than to a symbol that would not link.
|
||||
if (ns == "App") {
|
||||
if (meth == "splash_hide") {
|
||||
# the declaration lives in the pack prelude, and a splash is a packed
|
||||
# asset, so asking to dismiss one asks for the runtime that raised it
|
||||
use_pak()
|
||||
if g_windowed { emit(" call void @splash_hide()\n") }
|
||||
return val("0", "void")
|
||||
}
|
||||
perr(`unknown builtin App.{meth}`)
|
||||
}
|
||||
if (ns == "Pool") {
|
||||
if (meth == "capacity") { return val(itoa(MAX_ENT), "int") } # max entities
|
||||
if (meth == "reserved") { return val(emit_bind("load i32, ptr @L_entc"), "int") } # slots ever allocated (high-water)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue