Skip to content

feat(apps): iphone16-demo - #258

Open
NathanWalker wants to merge 5 commits into
pocket-stack:mainfrom
NathanWalker:feat/iphone16-demo
Open

feat(apps): iphone16-demo #258
NathanWalker wants to merge 5 commits into
pocket-stack:mainfrom
NathanWalker:feat/iphone16-demo

Conversation

@NathanWalker

@NathanWalker NathanWalker commented Aug 10, 2026

Copy link
Copy Markdown

Stacked on #257 (feat/tick-rate); only the last commit is new.

Following the apps/iphone2g-demo pattern, this adds apps/iphone16-demo.

Validated: bun tools/ios.ts build iphone16-demo --density=4 --hz=120 resolves the ios-dev plan and builds; bunx tsc --noEmit and bun test tests/ios-profile.test.ts pass.

🤖 Generated with Claude Code

NathanWalker and others added 4 commits August 9, 2026 11:51
…xternal-guest mode

A new engine/apple workspace member packaging modern iOS as a PocketJS
host. The composition mirrors hosts/pocketbook: one pocket_mod::Guest
realm, one pocket_ui_surface::UiSurface, and pocketjs_core::raster driven
incrementally through a DamageTracker, exposed behind a small C ABI.

- engine/apple/src/lib.rs — guest-owning mode: create/load_pak/eval_bundle/
  frame/render/hit_test_bounds/destroy, plus an effect channel over the
  ui.svc* ops (set_effect_callback drains guest svcSend lines during frame;
  post_event queues lines for the guest's next svcPoll).
- engine/apple/src/core_host.rs — external-guest mode: pocket_apple_core_*
  owns only the core, pak feed, raster pipeline, and svc queues, for hosts
  whose JS engine lives elsewhere (demonstrated with the NativeScript
  runtime evaluating the guest bundle in its own context).
- engine/apple/apple/PocketSurfaceView.{h,m} — CADisplayLink capped at
  60 Hz, latched touch contacts (a down+up between two ticks still reaches
  the guest as one present frame then a release), aspect-fit inverse touch
  mapping, damage-gated compositing of the ARGB32 framebuffer.
- engine/apple/build-xcframework.sh — clang-linked dynamic framework per
  slice (device arm64 + simulator arm64), no Xcode project.
- pocket-ui-surface additionally mounts hitTestBounds (spec op 42), the
  touch-path hit authority the gesture layer prefers over the ink-claiming
  hitTest.
- apps/nsengine — reference guest: an effect driver over svcSend, a
  per-frame poll pump, a focusable pressable button, and a platform-reach
  probe that distinguishes a sidecar realm from an embedding-runtime host.

rquickjs uses its bindgen feature: no pregenerated bindings exist for
aarch64-apple-ios targets. Validation: the render_hero example drives the
ABI end to end — 180 frames of apps/hero/main.tsx at 480x272 density 2
render non-blank and byte-identical across two independent instances.
Build guests with --density matching the surface density (glyphs bake at
build time; density 4 supersamples cleanly on 3x screens).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ll, play on the simulator

The CLI follow-up feat/apple-host's description names: a dev-profile target
id and a first-class run flow for the Apple host. `pocket play ios nsengine`
builds a guest from a resolved plan, stages it into a committed NativeScript
shell, and launches it on an arm64 iOS simulator.

tools/ios-profile.ts registers the transitional ios-dev profile (hostAbi 7,
platform ios, form embedded, fixed 480x272, raster density 1..4 as a registry
input, capabilities input.touch + text.glyphs.baked) following the
iphone2g-profile pattern: out of POCKET_TARGETS until device acceptance.
apps/nsengine gains a committed manifest — the root-template fallback demands
input.buttons, which this surface cannot advertise.

PocketSurfaceView now applies pocket_apple_set_identity("ios-dev", 7) at
init: plan-built bundles bake __POCKET_TARGET__/__POCKET_HOST_ABI__ and
assertNativeHostContract refuses hosts that publish nothing; the C ABI
existed but nothing called it. External-guest hosts (@nativescript/pocketjs)
mount the same pair on their ui namespace, and a source-text test guards the
agreement.

tools/ios.ts owns the flow — doctor/setup (symbian shape; Rust targets are
the only mutation, and only --rebuild-native needs them since the published
plugin ships a prebuilt PocketApple.xcframework), devices, native, build,
stage, and play (admissible-simulator pick, simctl boot, ns run ios
--device <udid> --no-hmr --justlaunch). --plugin-path/--runtime-tgz point the
shell at local builds for pre-publish validation and restore the committed
template afterwards. pocket play ios delegates here; bin.mjs gains the ios
passthrough.

hosts/apple/ns-shell is the committed shell: plan-driven (viewport, density,
and guest mode read from the staged plan + current.json), answers the ns.ping
service channel, and runs in place so repeat runs rebuild incrementally
(~47 s cold, ~16 s warm on Apple Silicon). Its tsconfig pins
@nativescript/core paths so the plugin's typings resolve from a file:
symlink.

Validated on the iOS 26.5 simulator in both guest modes: unprompted ns.ping
round trip renders pong 1, and --external-guest reads
UIDevice.currentDevice.systemVersion from guest code. Unit stage green
including tests/ios-profile.test.ts, the nsengine admission-matrix row, and
the CLI dispatch cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 1/60 s step becomes a per-realm constant chosen before the first tick:
Ui::set_tick_rate threads dt through the spring integrators and ms-to-frame
conversions (exact integer hz kept alongside dt so frame counts stay
byte-stable), UiSurface and the pocket-apple C ABI expose it
(pocket_apple_set_tick_rate / pocket_apple_core_set_tick_rate, gated on the
first tick), and PocketSurfaceView pins its CADisplayLink to the declared
rate. Guest-side virtual time bakes the same way glyphs do: tools/build.ts
--hz defines __POCKET_TICK_HZ__, clock/kinetics/input/deepzoom derive their
per-tick constants from it (the 60 path stays bit-for-bit the original), and
pocket ios gains --hz=60|120 staged through current.json to the shell. Hero's
headline reads the baked rate. Defaults everywhere remain 60, so existing
goldens, tapes, and bundles are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stock hero bakes PSP-demo defaults (Press Circle, 60 in the FPS
tile, "running on a 2005 handheld"). Wrap it like apps/iphone2g-demo
does so the ios-dev embedding shows its own device: Tap Me, an FPS tile
tied to the baked tick rate, and an iPhone 16 Pro device line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NathanWalker NathanWalker changed the title feat(apps): iphone16-demo — hero wrapper with device-true copy feat(apps): iphone16-demo Aug 10, 2026
@NathanWalker
NathanWalker marked this pull request as ready for review August 10, 2026 01:37
…ncher display union

Review fixes for pocket-stack#258:

- apps/iphone16-demo/pocket.json landed without its row in the
  admission-matrix test, so the unit gate failed (expected[demo]
  undefined for every committed manifest is the test's contract).
  iphone16-demo resolves exactly like nsengine: vita-only through the
  shared touch + integer-fit contract, private ios-dev otherwise.

- The committed launcher display registry (a cross-target union; each
  host intersects at runtime) regenerated via `bun tools/launcher.ts
  scan`, now carrying both vita-only demos, and the Vita-equality pin
  in tests/launcher-sim.test.ts evolves into subset + exact delta
  (["iphone16-demo-main", "nsengine-main"]). This overlaps the review
  commit on feat/ios-cli (f5a5975), which makes the same registry/test
  change for nsengine alone — whichever lands second reconciles
  trivially in the stack rebase.

Verified: `bun run test` 11/11 stages green (unit and launcher sim
both failed before this commit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@doodlewind

Copy link
Copy Markdown
Collaborator

Review — content is right, but the branch broke the unit gate; fixed and pushed

Verdict: the demo itself is clean — after the pushed fix the full gate is green, so this merges once the stack under it does (it stacks on #257, which has open design findings of its own).

What I ran

  • bun run test on this branch as pushed by the PR: FAIL in the unit stagetests/platform-contracts.test.ts "every committed demo manifest lands on the expected admission matrix" (378 pass / 1 fail): apps/iphone16-demo/pocket.json landed with no matrix row, and that test fails by design for every unlisted manifest. The PR body's validation ran bun test tests/ios-profile.test.ts only — the admission matrix and the launcher stages live elsewhere in the gate, which is exactly why bun run test is the bar.
  • With the pushed fix: 11/11 stages green in 40.5 s.
  • The demo renders end-to-end through the pocket-apple host (bun tools/build.ts iphone16-demo-main, then the render_hero example): 180 frames, non-blank, byte-identical across two instances — "Tap Me", the iPhone 16 Pro device line, and the FPS tile all correct. Built at --hz=120 the headline and tile read 120. (Caveat that belongs to feat(core): per-realm tick rate — fixed-step at a declared hz #257, not this PR: the host in that experiment never called set_tick_rate, and nothing objected to the mismatch.)

Pushed to this branch (822dc4c)

  1. "iphone16-demo": [false, true, false] in the admission matrix — it resolves exactly like nsengine (vita-only through the shared touch + integer-fit contract, private ios-dev otherwise).
  2. The launcher display union regenerated (bun tools/launcher.ts scan) with both vita-only demos, and the Vita-equality pin in tests/launcher-sim.test.ts evolved to subset + exact delta. Note: this overlaps the review commit on feat/ios-cli (f5a5975), which makes the same registry/test change for nsengine alone — whichever lands second reconciles trivially in the stack rebase.

Notes

  • app.tsx/main.tsx follow the repo conventions (framework imports from @pocketjs/framework/*, mounted entry, // @title header) and the manifest mirrors iphone2g-demo's shape with the 480×272 integer-fit viewport. Nothing else to flag in the 42 added lines.
  • One consequence of the manifest worth being deliberate about: like nsengine, this demo now appears in the committed launcher display union and is Vita-admissible by contract, so it shows up in Vita's Cover Flow deck. If iOS demos shouldn't ride along there, the exclusion belongs in the manifest/admission story, not the registry file.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants