Generated, ABI-checked raylib 6.0 bindings for GocciaScript 0.14.0. The bindings load raylib as a dynamic library and keep unsafe FFI use explicit.
- deterministic TypeScript bindings and declarations for all 600 functions of
the pinned official
raylib_api.json; - all 35 native struct layouts checked against
raylib.h; - a machine-readable skip report, currently empty;
- macOS and Linux dynamic-library discovery;
- typed basic-window, keyboard-input, embedded-image, Bunnymark, and playable raycasting examples, all with an on-screen FPS counter;
- a separately licensed DOOM port whose game logic and software renderer execute inside GocciaScript, with dynamic raylib used only for platform I/O;
- package, generator, ABI, and representative native-call validation.
Struct fields keep their exact native names. AudioStream.buffer is therefore
raylib's pointer field rather than the aggregate's backing store, which
FFI.metadata(value) returns as buffer, byteOffset, and size for every
aggregate.
const char * values and non-owned text returns use GocciaScript's UTF-8
string descriptor. Mutable char * arguments and owned char * returns remain
raw pointers so callers can mutate them and return the exact allocation to
UnloadFileText, UnloadUTF8, or MemFree. Pointer arguments accept native
pointers, buffers, typed arrays, aggregates, and null.
The documented nullable C-string parameters of LoadShader,
LoadShaderFromMemory, LoadAutomationEventList, and the four
text-replacement APIs use FFI.nullable("utf8string"), so they accept a string
or null.
TraceLog and TextFormat are bound as variadic functions. GocciaScript
requires the C variadic tail as exactly one typed FFI.varargs argument, even
when it is empty:
TextFormat("%s %03d", FFI.varargs(["utf8string", "i32"], ["raylib", 7]));
TraceLog(LOG_INFO, "ready", FFI.varargs([], []));GocciaScript ships no TypeScript declarations for its FFI global. Code that
is type-checked against bindings/raylib.d.ts declares it with the exported
GocciaFFI interface: declare const FFI: GocciaFFI;.
The official raylib 6.0 API describes 600 functions and all 600 are generated. SKIPPED.md remains the place where a future runtime or generator limit is recorded. No shim is presented as native coverage.
- GocciaScript 0.14.0
- raylib 6.0 built as a shared/dynamic library
- Node.js 24 or newer for host filesystem, hashing, compiler, and packaging orchestration
- a C11 compiler for ABI validation
On macOS:
brew install raylibOn Linux, install or build raylib 6.0 with BUILD_SHARED_LIBS=ON and make
libraylib.so discoverable. The generated module checks the current directory,
common /usr and /usr/local paths, and the platform loader path. A launcher
can set globalThis.RAYLIB_LIBRARY_PATH before dynamically importing the
bindings when raylib lives elsewhere.
GocciaScript denies host access by default. The repository goccia.json
requests only the ffi capability, and a config's request applies once you
have reviewed and trusted it:
GocciaRunner --trust goccia.jsonPass -P instead to accept the request for a single run without storing
trust.
The package entry point is the generated bindings, so an installed copy is imported by name:
import { InitWindow, closeRaylib } from "@frostney/gocciascript-raylib";GocciaScript resolves a package name only when the importing project grants
import for node_modules, and it installs the FFI global only with the
ffi grant. That project's own goccia.json therefore needs both, trusted
or accepted with -P like the request above:
{
"source-type": "module",
"permissions": {
"allow-ffi": true,
"allow-import": ["node_modules=."]
}
}Other files in the package, such as SKIPPED.json, stay importable by subpath.
Bindings that are instead imported by relative path from outside the importing
project need an allow-read grant for bindings/raylib.ts. Without the ffi
grant the bindings throw a TypeError that names it.
The examples are TypeScript, which GocciaScript parses as types-as-comments, and
use for...of; neither the traditional-for nor while compatibility flag
is required:
GocciaRunner examples/basic-window.ts
GocciaRunner examples/basic-input.ts
GocciaRunner examples/image-loading.ts
GocciaRunner examples/bunnymark.ts
GocciaRunner examples/doom-clone.tsBunnymark uses an immutable ESM byte import of the pinned raybunny.png; it
does not read an asset through a host filesystem API.
doom-clone.ts remains a small, clean-room MIT raycaster. The repository also
contains an actual DOOM engine example in the
separately licensed examples/doom-gpl/ subproject:
GocciaRunner --trust examples/doom-gpl/goccia.json
make -C examples/doom-gpl run IWAD=/absolute/path/to/doom.wad
# Or download checksummed, freely redistributable Freedoom data:
make -C examples/doom-gpl run-freedoomThe GPL subproject adapts a pinned pure-JavaScript LinuxDoom port into static GocciaScript TypeScript modules. WAD parsing, thinkers, AI, BSP traversal, and software rendering run in GocciaScript bytecode. There is no native or WebAssembly DOOM engine; dynamic raylib handles the window, input, framebuffer upload, and FPS overlay. No proprietary IWAD is committed, and the entire GPL directory is excluded from the MIT npm tarball.
The full engine legitimately uses classic for, while, and do...while
loops. Their compatibility flags are scoped to examples/doom-gpl/goccia.json,
which also requests ffi and read access to bindings/raylib.ts, because the
bindings live outside the subproject's own directory. The bindings and smaller MIT
examples do not enable those flags. The initial
GocciaScript 0.10.0 implementation rendered at roughly 3 FPS on an Apple M1
Max. The packed-palette fallback reaches 4.97 FPS, and presenting the indexed
framebuffer through a palette-texture shader reaches 6.32 FPS in controlled
steady-state measurements. It is genuine but not yet real-time.
A five-trial, uncapped comparison with 10,000 fixed-seed sprites found no performance case for compatibility loops:
| Loop | Mean observed FPS | Mean 25-frame time after startup |
|---|---|---|
current for...of |
4.06 | 6.3348 s |
indexed classic for |
3.96 | 6.4842 s |
indexed while |
4.02 | 6.3692 s |
The current for...of loop stays, and both legacy loop flags remain disabled.
That loop experiment predates the public-raylib instanced drawing path. With
GocciaScript's import-binding fast path held constant, the 10,000-sprite
workload measured 30.35 FPS versus the 12.99-FPS DrawTextureV baseline. The
renderer uses DrawMeshInstanced and a custom shader through the generated
bindings; it has no companion native library.
On GocciaScript 0.10.0 every read of an imported binding was slow enough that
the example copied its imports into local constants. GocciaScript 0.11.0 and
later retain resolved import bindings, so the example now calls its imports
directly.
Loop methodology,
the preceding application baseline,
the instancing validation, and
the import-alias removal are recorded
in the repository. benchmarks/fleet-bunnymark.sh repeats the 0.10.0 versus
0.14.0 comparison on any macOS or Linux machine and prints its table rows.
The pure API-repair and normalization core is GocciaScript-compatible
TypeScript in scripts/lib/api-core.ts, and the unit suite runs in
GocciaScript's own test runner in both interpreter and bytecode modes.
The thin .mjs host adapters remain Node scripts because regeneration and
validation must compute SHA-256 digests, invoke the C compiler, and inspect
npm pack. GocciaRunner exposes no host process or hashing API. Its sandbox
mode can write regenerated files back to the checkout through --copy-rw, but
that would move only the file I/O into GocciaScript, not those steps.
npm run vendor:update # only when intentionally refreshing pinned inputs
npm run generate
npm test
npm run check:generated
npm run test:types
GOCCIA_RUNNER=/path/to/GocciaRunner npm run test:abi
GOCCIA_RUNNER=/path/to/GocciaRunner npm run test:ffi
npm run pack:checknpm run test:types checks the declarations and a small consumer program
against them. RAYLIB_INCLUDE_DIR may point at a non-standard header directory
for the ABI test. GOCCIA_TEST_RUNNER selects the GocciaTestRunner binary
for npm test in the same way. The unit suite runs with
--ignore-config-permissions because it needs no capability, and the
native-call tests accept the config's ffi request for their own run, so
neither depends on a trust store. Regeneration verifies the pinned commit,
checksums, repaired JSON defect, inventory, and output determinism before
writing artifacts.
The Linux release lane can also be reproduced from macOS or Linux with:
docker build -f tests/linux/Dockerfile .- Update
metadata/raylib.jsonto an official stable tag and commit. - Record new source and asset SHA-256 values.
- Run
npm run vendor:update && npm run generate. - Review the normalized inventory and skip report.
- Run unit, ABI, FFI, package, and example smoke validation on macOS and Linux.
Do not silently accept upstream input drift: pin changes and the narrow JSON repair are reviewable release changes.
This package is available under the MIT License. raylib remains
under its upstream zlib license. examples/doom-gpl/ is an explicitly
separate GPL-2.0-only subproject and is excluded from the MIT npm package.
No proprietary IWAD is included anywhere.