P14.1: report sx std-library gaps surfaced by m3te
This commit is contained in:
184
docs/std-gaps.md
Normal file
184
docs/std-gaps.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# 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_s64`, `parse_f32`)
|
||||
- **m3te location:** `main.sx:156` (`parse_s64`), `main.sx:122` (`parse_f32`).
|
||||
- **What it does:** decimal ASCII `string` → `s64` / `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 `s64`
|
||||
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_s64` / `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`.
|
||||
Reference in New Issue
Block a user