Protocol method declarations now declare their receiver explicitly as the first
parameter — 'self: *Self' (or 'self: Self') — matching the impl method signature,
instead of the old implicit-receiver form where the listed params were only the
extra args. That asymmetry repeatedly caused confusion over whether the first
param was the receiver or an argument.
The parser validates the first param is 'self' typed Self/*Self, then strips it,
so all downstream lowering and the dispatch ABI are unchanged (impl blocks and
call sites are unaffected). A protocol method missing the receiver is now a parse
error.
Migrated all 129 protocol method signatures across library + examples (+ one
inline-sx test in sema.zig) to the explicit form. Updated specs.md + readme.md.
New: examples/0418-protocols-explicit-receiver.sx (feature),
examples/1190-diagnostics-protocol-missing-receiver.sx (negative/diagnostic).
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.
Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).
Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.
zig build test: 426/426; examples suite: 595/595.
What works on iOS sim now:
- pure-UIKit boot via UIApplicationMain (no SDL3 on iOS)
- SxGLView (CAEAGLLayer) + EAGLContext(GLES3) + CADisplayLink
- GLES3 shader path in modules/ui/renderer.sx (was wasm-only; now
wasm-OR-ios)
- UITouch -> ui.Event translation (mouse_down/moved/up) on touchesBegan/
Moved/Ended/Cancelled. Verified by tapping the chess board: the
expected pawn highlights and its legal moves show as green dots.
- chdir to NSBundle.mainBundle.resourcePath inside UIKitPlatform.init so
the game's relative fopen("assets/...") calls resolve.
Required restructuring to fix four problems discovered along the way:
1. GL context + load_gl must happen BEFORE UIApplicationMain so the
game's pipeline.init (which compiles shaders) doesn't crash on null
function pointers. Pulled EAGLContext creation + load_gl out of
didFinishLaunching: into UIKitPlatform.init via uikit_create_gl_context.
2. UIScreen.nativeScale returns CGFloat (=double on 64-bit Apple).
Reading it through a `(*void, *void) -> f32` msgSend signature
clobbers the value to 0 — the upper 32 bits of d0 land where the f32
reads from. Replaced msg_f with msg_d returning f64 (and added
msg_odbl for setContentScaleFactor: which takes CGFloat).
3. `xx <f64-call-result>` directly assigned to an f32 field through a
sema path lowers as `sitofp` (integer→float) on the double — LLVM
verification rejects it. Workaround: hoist into an `f64` local first.
4. The renderer was selecting the GLSL 330 core shader on every non-wasm
target, including iOS GLES3 where it silently fails to compile and
no quads render. Added OS == .ios to the GLES branch.
Game changes:
- main.sx: g_plat is now a boxed `Platform` (not concrete *SdlPlatform).
Backend chosen per-target via `inline if OS == .ios { ... }`. The
ESC-to-stop handling is OS-guarded (mobile apps don't quit on key
press, and SDL_Keycode references would force-link SDL on iOS).
- build.sx: iOS no longer adds SDL3; it adds UIKit + OpenGLES +
QuartzCore instead.
- delta_time and viewport dims are now mirrored to free globals so the
dock subsystem (`g_dock_delta_time = @g_delta_time`) and build_ui
layout decisions don't need a pointer through the boxed protocol.
Other:
- Added `stop()` to the Platform protocol (no-op on UIKitPlatform).
- examples/66-uikit-platform.sx updated: taps advance the clear color
(red → green → blue) — smoke test for the touch IMP wiring.
- shutdown() on UIKitPlatform is a no-op (mobile apps don't tear down).
Outstanding for next session:
- The Dynamic Island notch overlaps the top of the board because we
haven't read UIView.safeAreaInsets yet (CGRect/UIEdgeInsets struct
returns require a different msgSend ABI than we currently express).
- Keyboard observer (UIKeyboardWillChangeFrameNotification + animation
duration) — the load-bearing iOS feature.
- Real-device codesigning workflow for the new build.
Two more sx compiler bugs to file out of this work:
- xx(f64 call result) → f32 emits sitofp (problem #3 above).
- Inline `#import` inside `inline if` fails to parse (we worked around
by importing both backends unconditionally; the unused-backend's
Obj-C calls are gated by `inline if OS == .ios`).
First commit of the Phase 8 platform abstraction (see current/PLATFORM_PLAN.md):
- `library/modules/platform/types.sx` — `FrameContext` (viewport_w/h,
pixel_w/h, dpi_scale, delta_time) and `KeyboardState` (visible, height
+ `zero()`). `EdgeInsets`/`Point`/`Size` and `Event` are reused from
`modules/ui/types.sx` / `modules/ui/events.sx`.
- `library/modules/platform/api.sx` — `Platform :: protocol { init,
run_frame_loop, poll_events, begin_frame, end_frame, safe_insets,
keyboard, show_keyboard, hide_keyboard, shutdown }`. Protocol bodies
omit `self` (matches the `View`/`Allocator` convention).
- `run_frame_loop` takes `Closure()` so backends own the run loop:
SDL drives a `while !quit`, UIKit hands it to a `CADisplayLink` tick,
Emscripten hands it to `emscripten_set_main_loop`.
No backend yet. Regression suite still 50/50; game build still green.