Files
m3te/docs/std-gaps.md
swipelab 6f7d2f4db2 lang migration: rename signed integer types sN -> iN
Mechanical sweep of all .sx sources, plan docs, and tests/expected
snapshots for the sx language rename (s8/s16/s32/s64 -> i8/i16/i32/i64).
Verified: tools/run_tests.sh 23/23.

Note: the ios-sim build has 2 pre-existing 'restart' dot-call errors
from the sx opt-in UFCS change (sx a47ea14) — independent of this
rename (present pre-sweep); migrated in the follow-up commit.
2026-06-12 09:36:51 +03:00

10 KiB

sx std-library gaps surfaced by m3te

m3te is authored entirely in sx. While building it, a handful of general-purpose utilities had to be hand-rolled because the sx standard library / library modules don't provide them. This report enumerates those candidates — the ones that arguably belong in sx std (or a library module) — and, for each, records an explicit cross-check against the real sx library so the list contains true gaps only.

  • sx library checked: /Users/agra/projects/sx/library/modules/std.sx, math/ (math.sx, vector2.sx, matrix44.sx), std/ (objc.sx, cli.sx, hash.sx, json.sx, uikit.sx, objc_block.sx), process.sx, fs.sx, test.sx, ui/ (types.sx, animation.sx, image.sx, …), gpu/, platform/.
  • m3te source read: *.sx (main.sx, board.sx, board_view.sx, board_anim.sx, board_fx.sx, board_layout.sx, gem_anim.sx, audio.sx, swipe.sx, build.sx), tests/test.sx, tools/.

Highest-value candidates first. Borderline items are flagged. A separate "Already provided" section lists helpers m3te also hand-rolled but which the library does ship (so they are honestly not gaps) — included because the step asks for an honest accounting.


Category: Numerics / parsing

1. String → number parsing (parse_i64, parse_f32)

  • m3te location: main.sx:156 (parse_i64), main.sx:122 (parse_f32).
  • What it does: decimal ASCII stringi64 / f32 (sign + integer + optional fractional part).
  • Why general-purpose: the exact inverse of the formatters everyone already uses; needed by any program that reads numbers from argv, env, config, or a text file. Nothing game-specific about it.
  • Suggested home: modules/std (alongside int_to_string / float_to_string), or a dedicated modules/strings.
  • Not already in sx library (checked): std.sx ships only the forward direction — int_to_string (std.sx:68), uint_to_string, float_to_string (std.sx:121), int_to_hex_string — and no parse. No atoi/strtol/strtod/string_to_int anywhere under library/modules (grepped). std/json.sx has a Parser.parse_number (json.sx:547) but it is a private method consuming a JSON parser's buffer, not a reusable string → number.

2. Seedable deterministic PRNG (Rng / LCG)

  • m3te location: board.sx:55 (Rng struct: next_u32 board.sx:59, next_range board.sx:66), board.sx:71 (rng_seeded), constants board.sx:51-53.
  • What it does: a 32-bit linear-congruential generator carried in i64 and masked to 32 bits — next_u32(), next_range(n) (uniform-ish [0,n)), rng_seeded(seed).
  • Why general-purpose: a seedable, reproducible RNG is a textbook stdlib primitive (simulations, procedural generation, shuffles, tests). m3te's copy is written to be host-width-independent; only its consumer (pick_gem) is game-specific — the generator itself is not.
  • Suggested home: modules/math (or a new modules/random).
  • Not already in sx library (checked): no rand/rng/lcg/xorshift/ random/seed generator anywhere under library/modules (grepped names and the classic LCG constants 1664525 / 1013904223). std/hash.sx is SHA-256 only (content addressing, not a PRNG). math/math.sx has sin/cos/sqrt/ clamp/… but no randomness.

Category: FFI / system ergonomics

3. NUL-terminated C string (*u8) → sx string bridge (from_cstr)

  • m3te location: main.sx:108-117 (read_env, copying form: strlen + cstring + memcpy); audio.sx:144-148 (viewing form over a getcwd buffer using c_strlen). libc strlen is re-bound in both main.sx:28 and audio.sx:30.
  • What it does: turn a foreign NUL-terminated byte pointer into an sx string — either by copying (strlen + alloc + memcpy) or by building a length-bounded view.
  • Why general-purpose: every FFI surface that returns char* needs this (env vars, getcwd, UTF8String, libc results, …). It is the single most repeated FFI chore in m3te and has no game content.
  • Suggested home: modules/std (e.g. from_cstr(*u8) -> string plus a zero-copy cstr_view), and a public strlen binding so callers stop re-declaring it.
  • Not already in sx library (checked): process.env (process.sx:86) performs exactly this copy inline but does not expose it as a reusable helper, and re-binds strlen privately (process.sx:28). std/objc.sx exposes NSString.UTF8String returning [*]u8 (objc.sx:113) with no helper to convert it back to a string. No public from_cstr/cstr_to_string exists.

4. getcwd binding / cwd() helper

  • m3te location: audio.sx:29 (getcwd #foreign binding), used in load_system_sound (audio.sx:141) to absolutize a bundle-relative path.
  • What it does: read the process's current working directory into a string.
  • Why general-purpose: resolving relative paths to absolute is a routine filesystem need, unrelated to match-3.
  • Suggested home: modules/fs (next to basename/dirname) or modules/process.
  • Not already in sx library (checked): fs.sx binds open/read/mkdir/ rename/… and ships basename/dirname/path_join but no getcwd. process.sx binds getenv/popen/system but not getcwd. The platform modules bind only chdir (platform/uikit.sx:20, platform/sdl3.sx:14), never getcwd; platform/bundle.sx obtains a cwd by shelling out, not via a binding. (grepped getcwd/chdir/cwd.)

Category: Graphics (borderline — partly engine-specific)

5. Image file / RGBA buffer → GPU texture (load_texture, upload_rgba)

  • m3te location: board_view.sx:132 (load_texture: PNG path → texture handle), board_fx.sx:104 (upload_rgba: in-memory RGBA buffer → texture handle). Both branch gpu.create_texture vs a raw-GL fallback.
  • What it does: decode an image (via stbi_load) or take an RGBA buffer and upload it as a .rgba8 texture, returning the handle.
  • Why general-purpose: "load a PNG into a texture" is needed by any app that draws sprites; the decode + create_texture core is reusable.
  • Suggested home: modules/ui/image.sx (a loader to complement the existing ImageView) or modules/gpu.
  • Not already in sx library (checked): ui/image.sx defines ImageView which takes an already-uploaded texture_id — it does not load from disk. stbi_load appears only in m3te, never in library/modules/ui (grepped). The GPU layer exposes gpu.create_texture but no file/decode front-end.
  • Borderline: the raw-GL fallback branch is engine-specific; only the "decode → create_texture" half is cleanly general. Worth shipping as the GPU-only path; the GL fallback can stay app-side.

Category: Testing (borderline — a stub exists)

6. Failing assertion with source location (expect)

  • m3te location: tests/test.sx:10 (expect(cond, msg) → prints FAIL <file>:<line>: <msg> and exits non-zero via process.exit).
  • What it does: a test assertion that, on failure, reports the caller's file:line and fails the process so a harness/CI catches it.
  • Why general-purpose: every sx test suite needs a failing assertion; m3te's whole tests/ gate is built on this one helper.
  • Suggested home: modules/test.
  • Partially exists (checked): modules/test.sx ships only assert(condition) which prints "assertion failed\n" and does not exit (test.sx:3-7) — useless as a gate. process.assert(cond, msg) (process.sx:148) does report file:line and exit 1, so the capability is half-present but split awkwardly: the dedicated test module can't fail a run, and the failing variant lives in process. A real modules/test expect/ assert that reports location and fails is the gap.

Already provided by the sx library — NOT gaps (honest accounting)

m3te also re-implemented or could have reused these; the library does ship them, so they are explicitly not counted above:

  • clamp / lerp / min / max / abs / sign — m3te uses these throughout (e.g. gem_anim.sx:45, board_layout.sx:20, swipe.sx:31) and they all come from math/math.sx:11-37. Not hand-rolled; not a gap.
  • read_env (main.sx:108) — duplicates process.env (process.sx:86) almost line-for-line. m3te re-declares its own getenv (main.sx:27) instead of importing process, but the capability exists. Not a gap (it motivates #3, the reusable from_cstr underneath it).
  • ease_out_cubic / ease_in_quad (board_anim.sx:28-29) — identical to ui/animation.sx:19 and ui/animation.sx:13. Re-implemented only because board_anim imports ui/types but not all of ui/animation. Not a gap. Placement note: these are pure curves and arguably belong in modules/math too, so non-UI code can reach them without pulling in the UI module.
  • fx_lerp_u8 / per-channel colour lerp (board_fx.sx:97) — Color.lerp (ui/types.sx:124) already lerps all four channels. Not a gap.
  • NSString / NSLog bridging (used in audio.sx) — std/objc.sx ships NSLog (objc.sx:75) and an Into(*NSString) for string (objc.sx:119), which m3te uses via xx. Not a gap.

Summary (top gaps, ranked)

  1. String → number parsing (parse_i64 / parse_f32) — clean, universally needed, the missing inverse of the existing formatters.
  2. Seedable PRNG (Rng LCG) — textbook stdlib primitive, entirely absent.
  3. from_cstr C-string bridge (+ public strlen) — the most-repeated FFI chore; process.env inlines it but never exposes it.
  4. getcwd / cwd() — only chdir is bound; the read side is missing.
  5. Image-file → GPU texture loader — borderline; the decode-and-upload core is general, the GL fallback is app-side.
  6. expect test assertion — borderline; capability is split between a non-failing test.assert stub and process.assert.