Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.git
.cache
**/.cache
dist
node_modules
*.tgz
libraylib.dylib
libraylib.so
135 changes: 135 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: CI

on:
push:
branches:
- main
pull_request:

permissions:
contents: read

jobs:
validate:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-24.04
- macos-15
runs-on: ${{ matrix.os }}
timeout-minutes: 30

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm

- name: Install raylib build dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
cmake \
libasound2-dev \
libgl1-mesa-dev \
libglu1-mesa-dev \
libx11-dev \
libxcursor-dev \
libxi-dev \
libxinerama-dev \
libxrandr-dev \
ninja-build \
patch \
unzip \
xauth \
xvfb

- name: Build pinned raylib dynamic library
shell: bash
run: |
git clone --filter=blob:none https://github.com/raysan5/raylib.git "$RUNNER_TEMP/raylib"
git -C "$RUNNER_TEMP/raylib" checkout dbc56a87da87d973a9c5baa4e7438a9d20121d28
cmake \
-S "$RUNNER_TEMP/raylib" \
-B "$RUNNER_TEMP/raylib-build" \
-G Ninja \
-DBUILD_EXAMPLES=OFF \
-DBUILD_SHARED_LIBS=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$RUNNER_TEMP/raylib-install"
cmake --build "$RUNNER_TEMP/raylib-build"
cmake --install "$RUNNER_TEMP/raylib-build"
echo "RAYLIB_INCLUDE_DIR=$RUNNER_TEMP/raylib-install/include" >> "$GITHUB_ENV"
if [[ "$RUNNER_OS" == "macOS" ]]; then
ln -s "$RUNNER_TEMP/raylib-install/lib/libraylib.dylib" ./libraylib.dylib
else
ln -s "$RUNNER_TEMP/raylib-install/lib/libraylib.so" ./libraylib.so
echo "LD_LIBRARY_PATH=$RUNNER_TEMP/raylib-install/lib" >> "$GITHUB_ENV"
fi

- name: Download pinned GocciaScript release
shell: bash
run: |
case "$RUNNER_OS-$RUNNER_ARCH" in
macOS-ARM64)
asset="gocciascript-0.10.0-macos-arm64.zip"
;;
macOS-X64)
asset="gocciascript-0.10.0-macos-x64.zip"
;;
Linux-ARM64)
asset="gocciascript-0.10.0-linux-arm64.tar.gz"
;;
Linux-X64)
asset="gocciascript-0.10.0-linux-x64.tar.gz"
;;
*)
echo "Unsupported runner: $RUNNER_OS-$RUNNER_ARCH" >&2
exit 1
;;
esac
curl --fail --location \
"https://github.com/frostney/GocciaScript/releases/download/0.10.0/$asset" \
--output "$RUNNER_TEMP/$asset"
mkdir -p "$RUNNER_TEMP/gocciascript"
if [[ "$asset" == *.zip ]]; then
unzip -q "$RUNNER_TEMP/$asset" -d "$RUNNER_TEMP/gocciascript"
else
tar -xzf "$RUNNER_TEMP/$asset" -C "$RUNNER_TEMP/gocciascript"
fi
loader="$(find "$RUNNER_TEMP/gocciascript" -type f -name GocciaScriptLoader -print -quit)"
test_runner="$(find "$RUNNER_TEMP/gocciascript" -type f -name GocciaTestRunner -print -quit)"
chmod +x "$loader" "$test_runner"
echo "GOCCIA_LOADER=$loader" >> "$GITHUB_ENV"
echo "GOCCIA_TEST_RUNNER=$test_runner" >> "$GITHUB_ENV"

- run: npm ci
- run: npm run generate
- run: git diff --exit-code
- run: npm test
- run: npm run check:generated
- run: npm run test:types
- run: npm run test:abi
- run: npm run test:ffi
- run: npm run pack:check

- name: Example startup smokes
if: runner.os == 'Linux'
shell: bash
run: |
mkdir -p .cache
xvfb-run -a "$GOCCIA_LOADER" tests/fixtures/basic-window-smoke.ts
xvfb-run -a "$GOCCIA_LOADER" tests/fixtures/basic-input-smoke.ts
xvfb-run -a "$GOCCIA_LOADER" tests/fixtures/image-loading-smoke.ts
xvfb-run -a "$GOCCIA_LOADER" examples/smoke.ts
xvfb-run -a "$GOCCIA_LOADER" tests/fixtures/doom-smoke.ts
test -s .cache/doom-smoke.png

- name: Goccia-executed DOOM GPL smoke
if: runner.os == 'Linux'
shell: bash
run: xvfb-run -a make -C examples/doom-gpl smoke
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.cache/
dist/
node_modules/
*.tgz
.DS_Store
libraylib.dylib
libraylib.so
raylib.dll
19 changes: 19 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2026 John Stein

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
176 changes: 175 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,175 @@
# GocciaScript-Raylib
# GocciaScript raylib

Generated, ABI-checked [raylib 6.0](https://github.com/raysan5/raylib/releases/tag/6.0)
bindings for [GocciaScript 0.10.0](https://github.com/frostney/GocciaScript/releases/tag/0.10.0).
The bindings load raylib as a dynamic library and keep unsafe FFI use explicit.

## What is included

- deterministic TypeScript bindings and declarations generated from the pinned
official `raylib_api.json`;
- all 35 native struct layouts checked against `raylib.h`;
- a machine-readable stable-runtime skip report;
- 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.

GocciaScript reserves aggregate properties named `buffer` and `byteOffset`.
raylib's `AudioStream.buffer` is therefore exposed as `nativeBuffer`; the
generated `RAYLIB_FIELD_ALIASES` object records this sole 6.0 rename.

`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`.

Stable FFI cannot express a parameter that accepts either a JavaScript string
or a null pointer with one descriptor. `LoadShaderRaw`,
`LoadShaderFromMemoryRaw`, and `LoadAutomationEventListRaw` are therefore
generated as additional raw-pointer bindings for their documented nullable
C-string parameters. The four text-replacement APIs likewise expose `Raw`
variants for their nullable replacement argument. The normal exports remain
convenient for non-null strings.

The official raylib 6.0 API describes 600 functions. Three are intentionally
outside the 0.1 scope (`TraceLog`, `TextFormat`, and `DrawBillboardPro`).
GocciaScript 0.10.0 also rejects functions that mix a top-level `f32` argument
with other top-level argument types. Every other function is generated, and
[SKIPPED.md](SKIPPED.md) records the exact stable-version boundary. No shim is
presented as native coverage.

## Requirements

- GocciaScript 0.10.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:

```sh
brew install raylib
```

On 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.

## Run an example

The repository `goccia.json` opts into only the unsafe FFI extension. 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:

```sh
GocciaScriptLoader examples/basic-window.ts
GocciaScriptLoader examples/basic-input.ts
GocciaScriptLoader examples/image-loading.ts
GocciaScriptLoader examples/bunnymark.ts
GocciaScriptLoader examples/doom-clone.ts
```

Bunnymark 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](examples/doom-gpl/README.md) in the
separately licensed `examples/doom-gpl/` subproject:

```sh
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-freedoom
```

The 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`;
the bindings and smaller MIT examples do not enable them. The initial
GocciaScript 0.10.0 implementation renders at roughly 3 FPS on an Apple M1 Max,
so it is genuine but not yet real-time.

## Bunnymark performance

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 result is dominated by rendering and the per-sprite `DrawTextureV` FFI
crossing. The current `for...of` loop stays, and both legacy loop flags remain
disabled. [Full methodology and raw results](benchmarks/bunnymark-loop-syntax.md)
are recorded in the repository.

## Project scripts and tests

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 must
read and write the real checkout, compute SHA-256 digests, invoke the C
compiler, and inspect `npm pack`. Stable `GocciaScriptLoader` intentionally
does not expose ambient host filesystem or process APIs; the sandbox runner
uses a virtual filesystem whose writes are returned as a diff rather than
materialized into the checkout.

## Regenerate and validate

```sh
npm run vendor:update # only when intentionally refreshing pinned inputs
npm run generate
npm test
npm run check:generated
npm run test:types

GOCCIA_LOADER=/path/to/GocciaScriptLoader npm run test:abi
GOCCIA_LOADER=/path/to/GocciaScriptLoader npm run test:ffi
npm run pack:check
```

`RAYLIB_INCLUDE_DIR` may point at a non-standard header directory for the ABI
test. 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:

```sh
docker build -f tests/linux/Dockerfile .
```

## Upgrade policy

1. Update `metadata/raylib.json` to an official stable tag and commit.
2. Record new source and asset SHA-256 values.
3. Run `npm run vendor:update && npm run generate`.
4. Review the normalized inventory and skip report.
5. 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.

## License

This package is available under the [MIT License](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.
Loading
Loading