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
77 changes: 76 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ name: Release

# Fires on a version tag push (e.g. `v0.3.2`). Gates on the Tests workflow
# passing for the exact same commit, then creates/updates the GitHub Release
# and (once wired up) would publish to npm.
# and publishes the package to npm as @bloomengine/engine.
#
# The /release Claude Code skill in .claude/skills/release/ drives this end
# to end: it bumps the version in package.json, commits, tags, pushes, and
# waits for this workflow to go green.
#
# Authentication: npm trusted publishing. The @bloomengine/engine package is
# configured on npmjs.com with this workflow (Bloom-Engine/engine →
# .github/workflows/release.yml → job publish-npm) as a trusted publisher.
# `id-token: write` on the publish job is enough — `npm publish` exchanges
# the GitHub OIDC token for a short-lived publish credential, no NPM_TOKEN
# secret needed. Provenance attestation is automatic under this flow.

on:
push:
Expand Down Expand Up @@ -147,3 +154,71 @@ jobs:
else
echo "OK package.json version matches tag ($VERSION)"
fi

# ---------------------------------------------------------------------------
# Publish the package to npm as @bloomengine/engine. Runs after the GitHub
# Release so a failure here doesn't leave a dangling release-but-no-package
# state. Skips cleanly if the version is already on the registry, which
# keeps re-runs idempotent (workflow_dispatch on an existing tag won't
# double-publish or fail).
#
# We check out submodules recursively because scripts/prepack.sh refuses
# to ship a tarball without the JoltPhysics sources materialised — the
# package vendors them rather than relying on a postinstall git clone.
# ---------------------------------------------------------------------------
publish-npm:
needs: github-release
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # required for npm provenance attestations
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: actions/setup-node@v5
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"

- name: Resolve tag
id: tag
env:
DISPATCH_TAG: ${{ github.event.inputs.tag }}
run: |
if [ -n "$DISPATCH_TAG" ]; then
TAG="$DISPATCH_TAG"
else
TAG="${GITHUB_REF#refs/tags/}"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"

- name: Verify package.json version matches tag
env:
VERSION: ${{ steps.tag.outputs.version }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
PKG_VERSION=$(node -p "require('./package.json').version")
if [ "$PKG_VERSION" != "$VERSION" ]; then
echo "::error::Tag $TAG ($VERSION) does not match package.json ($PKG_VERSION) — refusing to publish."
exit 1
fi

- name: Check if version already published
id: check
run: |
PKG_NAME=$(node -p "require('./package.json').name")
PKG_VERSION=$(node -p "require('./package.json').version")
if npm view "$PKG_NAME@$PKG_VERSION" version >/dev/null 2>&1; then
echo "$PKG_NAME@$PKG_VERSION is already on the registry — skipping publish."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "$PKG_NAME@$PKG_VERSION not yet published — will publish."
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Publish to npm
if: steps.check.outputs.skip == 'false'
run: npm publish --provenance --access public
54 changes: 54 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# The `files:` field in package.json is the source of truth for what
# ships. This .npmignore is belt-and-suspenders — it strips artifacts
# from globbed directories that `files:` pulls in, so we never ship
# build output, IDE state, or local caches even by accident.

# Rust build output
target/
**/target/
*.rlib
*.rmeta

# wasm-pack output (built per consumer; not part of the package)
native/web/pkg/

# Native build dirs
native/third_party/bloom_jolt/build/
native/third_party/JoltPhysics/Build/

# Jolt submodule extras we don't need at consumer build time.
# We only ship JoltPhysics/Jolt/ (the actual sources our cmake builds
# against) plus LICENSE + README. Everything else is samples, viewer,
# docs, assets, tests — multi-MB and irrelevant for embedding.
native/third_party/JoltPhysics/.git
native/third_party/JoltPhysics/.github/
native/third_party/JoltPhysics/Assets/
native/third_party/JoltPhysics/Build/
native/third_party/JoltPhysics/Docs/
native/third_party/JoltPhysics/HelloWorld/
native/third_party/JoltPhysics/JoltViewer/
native/third_party/JoltPhysics/PerformanceTest/
native/third_party/JoltPhysics/Samples/
native/third_party/JoltPhysics/TestFramework/
native/third_party/JoltPhysics/UnitTests/
native/third_party/JoltPhysics/Doxyfile
native/third_party/JoltPhysics/run_doxygen.bat
native/third_party/JoltPhysics/sonar-project.properties
native/third_party/JoltPhysics/ContributorAgreement.md

# Perry build artifacts
*.ts.o
*_ts.o
.perry-cache/
dist/

# OS / editor junk
.DS_Store
.vscode/
.idea/
*.swp

# Local-only state never meant for the registry
.claude/
node_modules/
package-lock.json
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Bloom Engine

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.
44 changes: 33 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,33 @@
Write TypeScript. Ship native games — and now the web too.
Bloom compiles your game to Metal, DirectX 12, Vulkan, OpenGL, and WebGPU — one codebase for every platform.

## Install

```bash
npm install @bloomengine/engine
```

Or with your preferred package manager:

```bash
bun add @bloomengine/engine
pnpm add @bloomengine/engine
yarn add @bloomengine/engine
```

The npm package ships the TypeScript API alongside the engine's Rust sources and the bundled [JoltPhysics](https://github.com/jrouwe/JoltPhysics) C++ shim, so a single `install` is enough — there's no separate native download step.

You'll also need:

- **Perry** — the TypeScript AOT compiler that turns your game into a native binary or WASM module. It also drives the engine's native build.
- **Rust toolchain** ([rustup.rs](https://rustup.rs)) — Perry invokes Cargo to compile the engine's platform crate the first time you build for each target.
- For web builds only: [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) (`cargo install wasm-pack`).

## Quick Start

```typescript
import { initWindow, windowShouldClose, beginDrawing,
endDrawing, clearBackground, drawText, Colors } from "bloom";
endDrawing, clearBackground, drawText, Colors } from "@bloomengine/engine";

initWindow(800, 450, "My Game");

Expand All @@ -26,7 +48,7 @@ while (!windowShouldClose()) {
Use `runGame()` for code that works on both native and web:

```typescript
import { initWindow, runGame, clearBackground, drawText, Colors } from "bloom";
import { initWindow, runGame, clearBackground, drawText, Colors } from "@bloomengine/engine";

initWindow(800, 450, "My Game");

Expand Down Expand Up @@ -55,14 +77,14 @@ cd dist/web && python3 -m http.server 8080

| Module | Import | Description |
|--------|--------|-------------|
| **Core** | `bloom/core` | Window, game loop, input, timing |
| **Shapes** | `bloom/shapes` | 2D drawing + collision detection |
| **Textures** | `bloom/textures` | Image loading, sprite batching |
| **Text** | `bloom/text` | TTF/OTF font loading and rendering |
| **Audio** | `bloom/audio` | Sound effects + music streaming |
| **Models** | `bloom/models` | 3D model loading (glTF, OBJ), skeletal animation |
| **Math** | `bloom/math` | Vectors, matrices, quaternions, easing |
| **Physics** | `bloom/physics` | Jolt-backed rigid + soft bodies, character, vehicles ([docs](docs/physics.md)) |
| **Core** | `@bloomengine/engine/core` | Window, game loop, input, timing |
| **Shapes** | `@bloomengine/engine/shapes` | 2D drawing + collision detection |
| **Textures** | `@bloomengine/engine/textures` | Image loading, sprite batching |
| **Text** | `@bloomengine/engine/text` | TTF/OTF font loading and rendering |
| **Audio** | `@bloomengine/engine/audio` | Sound effects + music streaming |
| **Models** | `@bloomengine/engine/models` | 3D model loading (glTF, OBJ), skeletal animation |
| **Math** | `@bloomengine/engine/math` | Vectors, matrices, quaternions, easing |
| **Physics** | `@bloomengine/engine/physics` | Jolt-backed rigid + soft bodies, character, vehicles ([docs](docs/physics.md)) |

## Platforms

Expand Down Expand Up @@ -143,7 +165,7 @@ Bloom supports GPU-accelerated skeletal animation via glTF/GLB models. The pipel

```typescript
import { loadModel, loadModelAnimation, updateModelAnimation, drawModel,
getTime, Colors } from "bloom";
getTime, Colors } from "@bloomengine/engine";

const character = loadModel("assets/models/character.glb");
const anim = loadModelAnimation("assets/models/character.glb");
Expand Down
4 changes: 2 additions & 2 deletions docs/skeletal-animation.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Joint matrices are written to the GPU in `end_frame()` via `flush_joint_matrices
### Loading

```typescript
import { loadModel, loadModelAnimation, drawModel, updateModelAnimation } from "bloom";
import { loadModel, loadModelAnimation, drawModel, updateModelAnimation } from "@bloomengine/engine";

// Load the mesh (vertices with skin data: JOINTS_0 + WEIGHTS_0)
const model = loadModel("assets/models/character.glb");
Expand Down Expand Up @@ -179,7 +179,7 @@ drawModel(model, { x: playerX, y: playerY, z: playerZ }, 1.0, WHITE);
```typescript
import { initWindow, windowShouldClose, beginDrawing, endDrawing,
clearBackground, loadModel, loadModelAnimation,
updateModelAnimation, drawModel, getTime, Colors } from "bloom";
updateModelAnimation, drawModel, getTime, Colors } from "@bloomengine/engine";

initWindow(800, 600, "Animation Demo");

Expand Down
4 changes: 2 additions & 2 deletions docs/web-target.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ python3 -m http.server 8080
Browsers cannot run blocking `while` loops. Use `runGame()` instead:

```typescript
import { initWindow, runGame, clearBackground, drawRect, Colors } from "bloom";
import { initWindow, runGame, clearBackground, drawRect, Colors } from "@bloomengine/engine";

initWindow(800, 600, "My Game");

Expand Down Expand Up @@ -113,7 +113,7 @@ if (fileExists("save.json")) {
## Platform Detection

```typescript
import { getPlatform, Platform } from "bloom";
import { getPlatform, Platform } from "@bloomengine/engine";

if (getPlatform() === Platform.WEB) {
// web-specific code
Expand Down
1 change: 1 addition & 0 deletions native/shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod postfx;
pub mod custom_shaders;
pub mod staging;
pub mod profiler;
pub mod sdf_cache;
// Jolt C ABI + Rust wrapper live on native only. On wasm32 the web crate
// routes bloom_physics_* calls through wasm_bindgen to JoltPhysics.js;
// no Rust-side Jolt integration is needed.
Expand Down
Loading
Loading