`ludic bundle` builds an AppIcon.icns and macOS reads it out of the .app, so a shipped game has an icon. `ludic build` produces a bare executable: no bundle, no CFBundleIconFile, and so no icon at all - macOS draws the generic green "exec" tile. That is the build a developer runs every day, which is why "the game has no icon" can be true for months while the bundle is perfect. It was here: the .app's icns validated against iconutil, the plist was right, the signature was right, and NSWorkspace rendered the artwork - and the binary beside it still had the exec tile. App.set_icon(path) takes the bytes through lp_pak_open, so a packed path and a loose one both work and this does not repeat Audio.load's trick of taking a filesystem path only. NSData copies them, so the buffer goes straight back. A missing or undecodable image leaves the existing icon alone rather than clearing it; a bundled app is unaffected; headless links no AppKit and compiles it away. Verified the Cocoa sequence against an ObjC twin doing the same message sends: before, a bare binary's applicationIconImage is the generic 128x128 tile; after, it is the 1024x1024 artwork. Backend change, so selfhost/ludicc.seed.ll is reseeded: the bootstrap fixpoint and the C-free rebuild from the seed both pass.
984 B
bump: minor
type: feat
App.set_icon(path) — an icon for a binary that has no bundle to take one from.
ludic bundle builds an AppIcon.icns and macOS reads it from the .app, so a
shipped game has an icon. ludic build produces a bare executable, which has no
bundle, no CFBundleIconFile and therefore no icon at all — macOS draws the generic
green "exec" tile in the Dock. That is the build a developer runs all day and the one
they see every session, so "my game has no icon" is true long before it ships.
App.set_icon("assets/app/icon.png")
The path resolves through the pack first and the filesystem second, like every other
asset, so the same call works packed and unpacked — it does not repeat Audio.load's
trick of taking a filesystem path only. An image that cannot be found or decoded
leaves the current icon alone rather than clearing it, calling it in a bundled app is
a harmless no-op, and a headless build compiles it away to nothing.