Parent: #126
Problem
Bloom exposes hundreds of TypeScript functions across a native-library manifest, shared FFI macros/modules, and per-platform exports. Hand-maintained surfaces drift, and some public APIs still ask Perry callers to supply native pointers or arrays that are not naturally expressible. Examples include collection-heavy operations around scene-node LODs and polygon extrusion, plus legacy pointer-based texture-array APIs (new helper variants improve individual cases but not the underlying contract).
This undermines both simplicity and cross-platform truth: an API can exist in TypeScript yet be missing, stubbed, or practically uncallable on a target.
Outcome
A single declarative FFI schema that generates/validates the manifest, Rust export surface, TypeScript declarations/wrappers, platform capability metadata, and documentation; collection APIs use Perry-safe handles/builders/packed buffers instead of raw pointers.
Schema contract
For every symbol, record:
- stable FFI name and public TypeScript name/module;
- parameter/return types, ownership, nullability, units, and error behavior;
- string encoding and lifetime;
- availability/capability per platform;
- whether implementation is shared, platform-specific, unsupported, or deprecated;
- version/deprecation metadata and doc text/examples;
- threading/frame-phase constraints where relevant.
Generate deterministic output and provide --check mode that fails if checked-in generated files are stale.
Supported ABI shapes
Keep the primitive ABI small and explicit:
- scalars and handles;
- UTF-8 strings with length or a documented Perry string bridge;
- packed typed buffers referenced by handle + offset/count;
- builder APIs for complex variable-length structures;
- structured result/error via numeric code + retrievable message or a proven Perry-safe representation.
Do not expose *const T semantics directly as a friendly TypeScript API.
Collection migration examples
extrudePolygon: accept a reusable vec2/float buffer handle or polygon-builder handle plus point count.
- scene-node LODs: builder or packed records containing model handle, threshold, and options.
- texture arrays: keep file/texel-friendly helpers; deprecate raw-pointer entry points from public docs once all use cases have safe equivalents.
The wrappers should make one-shot use concise while allowing buffer reuse in hot paths.
Platform parity
- Shared generated stubs may only represent unsupported with an explicit error/capability, never silent success.
tools/validate-ffi.js should validate generated freshness, arity/types, implementation availability, and schema-to-package coverage.
- Web/watchOS/platform exceptions must live in the schema with reason, not parser allowlists spread across tools.
Acceptance criteria
Likely files
- new schema/generator under
tools/
package.json
src/**/*.ts
native/shared/src/ffi_core/, native/shared/src/ffi.rs
native/*/src/lib.rs
tools/validate-ffi.js
Non-goals
- Generating engine implementation logic.
- An unconstrained general serialization protocol.
- Breaking all existing handle-based APIs.
Dependencies
None. Streaming/components/UI should consume the resulting safe patterns rather than invent new ABI shapes.
Parent: #126
Problem
Bloom exposes hundreds of TypeScript functions across a native-library manifest, shared FFI macros/modules, and per-platform exports. Hand-maintained surfaces drift, and some public APIs still ask Perry callers to supply native pointers or arrays that are not naturally expressible. Examples include collection-heavy operations around scene-node LODs and polygon extrusion, plus legacy pointer-based texture-array APIs (new helper variants improve individual cases but not the underlying contract).
This undermines both simplicity and cross-platform truth: an API can exist in TypeScript yet be missing, stubbed, or practically uncallable on a target.
Outcome
A single declarative FFI schema that generates/validates the manifest, Rust export surface, TypeScript declarations/wrappers, platform capability metadata, and documentation; collection APIs use Perry-safe handles/builders/packed buffers instead of raw pointers.
Schema contract
For every symbol, record:
Generate deterministic output and provide
--checkmode that fails if checked-in generated files are stale.Supported ABI shapes
Keep the primitive ABI small and explicit:
Do not expose
*const Tsemantics directly as a friendly TypeScript API.Collection migration examples
extrudePolygon: accept a reusable vec2/float buffer handle or polygon-builder handle plus point count.The wrappers should make one-shot use concise while allowing buffer reuse in hot paths.
Platform parity
tools/validate-ffi.jsshould validate generated freshness, arity/types, implementation availability, and schema-to-package coverage.Acceptance criteria
as anypointer workaround.Likely files
tools/package.jsonsrc/**/*.tsnative/shared/src/ffi_core/,native/shared/src/ffi.rsnative/*/src/lib.rstools/validate-ffi.jsNon-goals
Dependencies
None. Streaming/components/UI should consume the resulting safe patterns rather than invent new ABI shapes.