Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
588def8
690-document-base-bplusindex-skill: Document the BPlusIndex subsystem…
kalevski Jun 23, 2026
3d026d0
691-add-base-bplusindex-demo: Add a BPlusIndex interactive demo to th…
kalevski Jun 23, 2026
8138e90
692-fix-logging-skill-core-api: Correct false claims and complete the…
kalevski Jun 23, 2026
8810102
693-document-logging-node-exports: Document the @toolcase/logging/nod…
kalevski Jun 23, 2026
ed8af75
694-fix-serializer-skill-doc-bugs: Fix copy-paste-breaking doc bugs i…
kalevski Jun 23, 2026
5dd614c
695-serializer-enum-map-packed-demo: Add a serializer demo for enum, …
kalevski Jun 23, 2026
f9db9af
696-serializer-versioning-demo: Add a serializer versioning & migrati…
kalevski Jun 23, 2026
4517af3
697-serializer-streaming-demo: Add a serializer streaming (fragment/r…
kalevski Jun 23, 2026
7892169
698-serializer-validate-safe-introspection-demo: Add serializer valid…
kalevski Jun 23, 2026
a68c69e
699-document-node-store-module: Document the @toolcase/node store/ mo…
kalevski Jun 23, 2026
62ce900
700-add-node-store-demo: Add a demo for the @toolcase/node store/ module
kalevski Jun 23, 2026
c4c8e44
701-document-node-verifycallback: Document verifyCallback (OAuth2 CSR…
kalevski Jun 23, 2026
cc3e4d5
702-fix-nodepage-landing: Fix the @toolcase/node landing page (NodePa…
kalevski Jun 23, 2026
52db346
703-add-node-oauth2-demo: Add an OAuth2/OIDC demo for @toolcase/node
kalevski Jun 23, 2026
8077dea
707-fix-phaser-plus-skill-structs-peers: Fix the Structs surface and …
kalevski Jun 23, 2026
b1f8348
710-phaser-export-reactfeature-root: Export ReactFeature from the pha…
kalevski Jun 23, 2026
c2228c8
711-update-phaser-game-dev-skill: Update the phaser-game-dev architec…
kalevski Jun 23, 2026
96abd8f
713-document-tc-accordion-skill: Document tc-accordion and tc-accordi…
kalevski Jun 23, 2026
4712306
714-expand-nginxpilot-landing: Expand the nginxpilot landing page wit…
kalevski Jun 23, 2026
2a19d9e
715-fix-taskforge-landing-page: Correct and expand the TaskForge land…
kalevski Jun 23, 2026
ad34e8f
716-create-taskforge-skill: Create a full SKILL.md for taskforge
kalevski Jun 23, 2026
99a055a
704-add-node-imaging-demo: Add an imaging demo (ImageProcessor + Atla…
kalevski Jun 24, 2026
0200ee1
705-add-node-env-demo: Add an env typed-loader demo for @toolcase/node
kalevski Jun 24, 2026
a7e18ad
706-fix-kvservice-demo-real-classes: Make KVServiceDemo exercise the …
kalevski Jun 24, 2026
d6b6946
708-phaser-broadphase-demo-scene: Add a broad-phase demo scene (Spati…
kalevski Jun 24, 2026
5e50d90
709-phaser-cameraflash-dialogcue-scenes: Add standalone CameraFlash a…
kalevski Jun 24, 2026
5ea568b
712-document-demo-tc-theme: Document and demo tc-theme in @toolcase/w…
kalevski Jun 24, 2026
aa7a401
extended nginxpilot
kalevski Jun 24, 2026
ba8dc0d
latest updates
kalevski Jun 24, 2026
7318f3a
package-lock.json updated
kalevski Jun 24, 2026
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
1 change: 1 addition & 0 deletions base/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export type { Delta, ObjectDelta, ArrayDelta, Edit } from './diff'
export type { FormatNumberOptions } from './formatNumber'

export type { StorageAdapter, BPlusIndexOptions, RangeOptions, BPlusIndexStats } from './BPlusIndex'
export { SpatialHash, Quadtree } from './spatial'
export type { SpatialPoint, SpatialRect } from './spatial'
export type { EasingFn } from './easing'
export type { Rect } from './Vec2'
Expand Down
3 changes: 2 additions & 1 deletion base/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src"
"rootDir": "./src",
"types": ["node"]
},
"include": ["src"]
}
315 changes: 312 additions & 3 deletions examples/public/base/SKILL.md

Large diffs are not rendered by default.

281 changes: 263 additions & 18 deletions examples/public/logging/SKILL.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/public/node-service/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Optional packages that slot into this scaffold. Use what fits.
- `HTTP.RESTResponse` / `HTTP.RESTError` / `HTTP.Status` for every router response.
Optional: `Cache`, `EventEmitter`, `Broadcast`, `generateId`, `retry`, `ObjectPool`, `JSONSchema` validation, `PriorityQueue`, `VectorClock`, `Color`, `Dijkstra`, `AStar`, `WeightedRandom`, `Packing.Packer`. Live in `util/` or `services/`.

- **`@toolcase/node`** — Backend helpers. Mandatory: `env(name, default, type)` for every env var read in `src/env.ts`. Recommended for the db layer: `BaseRepository` (engine-agnostic — ships no queries; you implement the verbs with raw SQL, see `db/`) + `EntityService` (before/after hooks) + `RESTRouteHandler` (auto-CRUD over an `EntityService`). Also: `HttpServer` wrapper, `KVService` (Redis), `ImageProcessor` (sharp), `AtlasBuilder`, `OAuth2` helpers. Peer-deps `@toolcase/base`, `fastify`, `redis`, `sharp`, `jose` declared optional — install only what you use. `BaseRepository` is driver-free: pass it your query runner (the `pg` `Pool`/`PoolClient`, or the `node:sqlite` `DatabaseSync`); the driver stays the project's own dep.
- **`@toolcase/node`** — Backend helpers. Mandatory: `env(name, default, type)` for every env var read in `src/env.ts`. Recommended for the db layer: `BaseRepository` (engine-agnostic — ships no queries; you implement the verbs with raw SQL, see `db/`) + `EntityService` (before/after hooks) + `RESTRouteHandler` (auto-CRUD over an `EntityService`). Also: `HttpServer` wrapper, `KVService` (Redis), `NodeStore` + `BlockStore` (durable file-based key-value store: B+ tree index + append-mostly heap, no extra peer deps — useful for embedded caches or local worker state that must survive process restarts without a Redis dependency), `ImageProcessor` (sharp), `AtlasBuilder`, `OAuth2` helpers. Peer-deps `@toolcase/base`, `fastify`, `redis`, `sharp`, `jose` declared optional — install only what you use. `BaseRepository` is driver-free: pass it your query runner (the `pg` `Pool`/`PoolClient`, or the `node:sqlite` `DatabaseSync`); the driver stays the project's own dep.

- **`@toolcase/logging`** — Isomorphic logger. Mandatory. One named logger per class: `logging.getLogger('Database')`, `logging.getLogger('Http')`, `logging.getLogger('UserService')`. Configure level + reporters once at boot in `index.ts` (default reporter is console; swap via custom `LogReporter`, `JSONLineReporter`, `BufferedReporter`, or `FileLogReporter` from `@toolcase/logging/node`).

Expand Down
203 changes: 202 additions & 1 deletion examples/public/node/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: node
description: Use when reaching for @toolcase/node — backend helpers for Node.js. Single entrypoint exposing Fastify endpoints (RouteHandler, RESTRouteHandler, Router, HttpServer), engine-agnostic repositories (BaseRepository, EntityService — you implement the verbs for your engine), Redis KV service (KVService — Locker, RateLimiter, Leaderboard, ValueStore, Versioned, SubscriberPool, KeyBuilder, LuaScriptCache), OAuth2/OIDC helpers, ImageProcessor + AtlasBuilder (sharp), typed env() loader, plus isomorphic sanitize / pagination / filter / sort / domain-error helpers and FieldSchema → JSON Schema derivation.
description: Use when reaching for @toolcase/node — backend helpers for Node.js. Single entrypoint exposing Fastify endpoints (RouteHandler, RESTRouteHandler, Router, HttpServer), engine-agnostic repositories (BaseRepository, EntityService — you implement the verbs for your engine), Redis KV service (KVService — Locker, RateLimiter, Leaderboard, ValueStore, Versioned, SubscriberPool, KeyBuilder, LuaScriptCache), NodeStore + BlockStore (two-file persistent key-value store: B+ tree index + append-mostly heap), OAuth2/OIDC helpers, ImageProcessor + AtlasBuilder (sharp), typed env() loader, plus isomorphic sanitize / pagination / filter / sort / domain-error helpers and FieldSchema → JSON Schema derivation.
---

# @toolcase/node — API Reference
Expand All @@ -24,6 +24,9 @@ import {
type Filter, type Sort, FILTER_OP_SET, encodeCursor, decodeCursor,
// redis peers: redis, @toolcase/serializer (serializer optional)
KVService, KeyBuilder, LuaScriptCache, KV_LUA_SCRIPTS,
// file store — no extra peers beyond node:fs (built-in)
NodeStore, BlockStore, type NodeStoreOptions, type BlockRef,
serializeBlockRef, deserializeBlockRef, BLOCK_REF_SIZE,
} from '@toolcase/node'
```

Expand All @@ -34,6 +37,7 @@ import {
| `BaseRepository`, `EntityService` | `@toolcase/base` — engine-agnostic; you implement the verbs against any driver |
| `KVService` (string surface only) | `@toolcase/base`, `redis` |
| `KVService.*Value*` methods (typed binary) | adds `@toolcase/serializer` |
| `NodeStore`, `BlockStore` | `@toolcase/base`, `node:fs` (Node built-in) |
| `ImageProcessor`, `AtlasBuilder` | `@toolcase/base`, `sharp` |

---
Expand Down Expand Up @@ -1209,6 +1213,57 @@ const nonce = generateNonce() // base64url, 32 bytes

`byteLength < 16` throws. `generatePKCE('plain')` returns `codeChallenge === codeVerifier`.

### `callback.ts` — CSRF state verification

```ts
import { verifyCallback, type VerifyCallbackInput } from '@toolcase/node'

interface VerifyCallbackInput {
stored: string // state your server generated and saved before the authorize redirect
received: string // state returned by the authorization server in the callback URL
}

// Constant-time CSRF guard.
// Throws OAuth2CallbackError('state mismatch') when lengths differ or values do not match.
function verifyCallback(input: VerifyCallbackInput): void
```

`state` is the CSRF token for the Authorization Code flow. A naive `===` check is vulnerable to timing side-channels: an attacker who can measure response latency may infer characters of the stored value one byte at a time. `verifyCallback` wraps Node's `crypto.timingSafeEqual` to close that window.

Call it as the first action inside the callback handler — before `exchangeCode` — so a forged `state` is rejected before any token request is made.

```ts
// authorize handler — store state inside the session blob alongside the rest of the flow data
const state = generateState()
const nonce = generateNonce()
const pkce = generatePKCE()

await kv.set(`oauth2:session:${sessionId}`, JSON.stringify({
state, nonce, codeVerifier: pkce.codeVerifier, redirectUri
}), { EX: 600 })

reply.redirect(303, buildAuthorizeURL(provider, {
state, nonce,
codeChallenge: pkce.codeChallenge, codeChallengeMethod: pkce.method,
redirectUri, scope: ['openid', 'email', 'profile']
}))

// callback handler
const { code, state: receivedState, error, error_description } = req.query
if (error) throw new OAuth2CallbackError(error, error_description)

const raw = await kv.getDel(`oauth2:session:${sessionId}`)
if (!raw) throw new OAuth2ProtocolError('session_missing_or_expired')
const flow = JSON.parse(raw)

verifyCallback({ stored: flow.state, received: receivedState }) // CSRF guard — before exchangeCode

const tokens = await exchangeCode(provider, { code, codeVerifier: flow.codeVerifier, redirectUri: flow.redirectUri })
// then verifyIdToken(..., { nonce: flow.nonce, ... })
```

> **Nonce replay protection.** `verifyCallback` guards only `state`. Always also pass `nonce` to `verifyIdToken` — omitting it silently skips the nonce check and leaves the OIDC flow open to replay attacks.

### `flow.ts` — Authorization Code

```ts
Expand Down Expand Up @@ -1410,3 +1465,149 @@ const discord = defineOAuth2Provider({
defaultScope: ['identify', 'email']
})
```

---

## Store

Two-file persistent key-value store backed by a B+ tree index and an append-mostly data heap. Node-only (uses `FileHandle` from `node:fs/promises`). No extra peer deps beyond `@toolcase/base`. Storage is split across:

- `<path>.idx` — `BPlusIndex<K, BlockRef>` from `@toolcase/base` (managed by `FsAdapter`), maps keys to 12-byte `BlockRef` pointers.
- `<path>.dat.N` — append-mostly heap segments; each block is a raw serialised value blob.

### BlockRef / helpers

```ts
interface BlockRef {
segment: number // uint32 — which .dat.N segment file
offset: number // uint32 — byte offset within that segment
length: number // uint32 — byte length of the block
}

const BLOCK_REF_SIZE = 12 // bytes (fixed-size struct)

function serializeBlockRef(ref: BlockRef): Uint8Array
function deserializeBlockRef(buf: Uint8Array): BlockRef
```

### NodeStoreOptions<K, V>

```ts
interface NodeStoreOptions<K, V> {
path: string // base path; .idx and .dat.N are appended
compare: (a: K, b: K) => number // key ordering function
serializeKey: (k: K) => Uint8Array
deserializeKey: (b: Uint8Array) => K
serializeValue: (v: V) => Uint8Array
deserializeValue: (b: Uint8Array) => V
keyEncoding?: string // forwarded to BPlusIndex for guard checks
pageSize?: number // B+ tree page size in bytes (default 4096)
}
```

`BPlusIndex.keyPreset` from `@toolcase/base` supplies pre-wired `{ compare, serializeKey, deserializeKey, keyEncoding }` bundles for `'string'`, `'number'`, and `'bigint'` key types — spread one into the options to skip the boilerplate:

```ts
const store = await NodeStore.open({
path: '/data/mystore',
...BPlusIndex.keyPreset.string,
serializeValue: v => new TextEncoder().encode(v),
deserializeValue: b => new TextDecoder().decode(b),
})
```

### NodeStore<K, V>

```ts
class NodeStore<K, V> {
static open<K, V>(opts: NodeStoreOptions<K, V>): Promise<NodeStore<K, V>>

// Accessors
get size(): number // live entry count
get liveRatio(): number // live heap bytes / total heap bytes; 1.0 = no dead space

// CRUD
get(key: K): Promise<V | undefined>
set(key: K, value: V): Promise<void>
delete(key: K): Promise<boolean>

// Iteration — all in B+ tree key order
entries(): AsyncGenerator<[K, V]>
range(opts?: RangeOptions<K>): AsyncGenerator<[K, V]> // bounded scan; RangeOptions<K> from @toolcase/base
keys(): AsyncGenerator<K>
values(): AsyncGenerator<V>

// Maintenance
optimize(): Promise<void> // compact: copy live blocks to new segment, reclaim dead space
flush(): Promise<void> // fsync both heap and index
close(): Promise<void>
}
```

`RangeOptions<K>` from `@toolcase/base`: `{ gte?, gt?, lte?, lt? }` compared with the store's `compare` function.

```ts
import { NodeStore } from '@toolcase/node'
import { BPlusIndex } from '@toolcase/base'

const store = await NodeStore.open({
path: '/data/mystore',
...BPlusIndex.keyPreset.string,
serializeValue: v => new TextEncoder().encode(v),
deserializeValue: b => new TextDecoder().decode(b),
})

await store.set('hello', 'world')
const v = await store.get('hello') // 'world'
await store.delete('hello')

// Bounded range scan
for await (const [key, value] of store.range({ gte: 'a', lte: 'z' })) {
console.log(key, value)
}

// Compaction metrics and maintenance
console.log(store.liveRatio) // < 1 means dead heap space; run optimize() to reclaim
await store.optimize()
await store.flush()
await store.close()
```

**Crash safety.** `set()` fsyncs the heap segment before committing the index entry — a crash between the two steps leaves at most a harmless orphan block in `.dat.N`. The index stays consistent. On the next `open()`, segment files not referenced by the index are detected as crash orphans and deleted automatically.

**Compaction (`optimize()`).** Copies all live blocks to a fresh segment in key order, atomically updates the index with a single `setMany` call (one superblock commit), then deletes the old segments. A crash before the `setMany` leaves the new segment as an orphan, cleaned up on `open()`. A crash after `setMany` but before old-segment deletion leaves extra files that are cleaned up on `open()` — both paths are safe.

**`NodeStore` vs `KVService`.** Use `NodeStore` for durable file-based storage with no infrastructure dependency (embedded caches, local feature stores, offline-capable worker state). Use `KVService` (Redis) for distributed in-memory state with TTL, pub/sub, and horizontal scale.

### BlockStore

Append-mostly heap manager that `NodeStore` wraps internally. Exported as a public API for advanced use cases that need raw segment-level access (e.g. a standalone append-only log).

```ts
class BlockStore {
constructor(basePath: string)

segmentPath(segId: number): string

get activeSegment(): number
get activeOffset(): number

// Discovery — returns { segId → file size } for all .dat.N files found on disk
scanSegments(): Promise<Map<number, number>>
setActive(segId: number, writeOffset: number): void
nextSegmentId(): number // returns activeSegment + 1 (for a fresh compaction segment)

// I/O
append(data: Uint8Array): Promise<BlockRef> // write to active segment
read(ref: BlockRef): Promise<Uint8Array> // read any block by ref
writeAt(segId: number, offset: number, data: Uint8Array): Promise<void> // used during compaction

// Durability
fsyncActive(): Promise<void>
fsyncSegment(segId: number): Promise<void>

// Lifecycle
deleteSegment(segId: number): Promise<void> // closes handle + unlinks file; unlink errors swallowed
close(): Promise<void>
}
```
28 changes: 26 additions & 2 deletions examples/public/phaser-game-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Opinionated blueprint for Phaser 4 games. Layered, registry-driven, ESM. Every s
Stack baseline:

- Phaser 4 (`phaser ^4.x`).
- `@toolcase/phaser-plus` ^4.x — **required** runtime layer. Top-level exports: `Engine`, `Scene`, `GameObject`, `Feature`, `FeatureRegistry`, `ServiceRegistry`, `GameObjectPool`, `Layer`, `ObjectLayer`, `HTMLFeature`, `SplitScreen`, `LogLevel`, plus `Events`, `Flow`, `Structs` namespaces. Re-exports from `effects/` (`installEffects`, shader effects), `perspective2d/` (`Scene2D`, `World`, `GameObject2D`, `Grid`), `cinema/`, `input/`, `ai/`, `flow/`, `debugger/`.
- `@toolcase/phaser-plus` ^4.x — **required** runtime layer. Top-level exports: `Engine`, `Scene`, `GameObject`, `Feature`, `FeatureRegistry`, `ServiceRegistry`, `GameObjectPool`, `Layer`, `ObjectLayer`, `HTMLFeature`, `SplitScreen`, `LogLevel`, plus `Events`, `Flow`, `Structs` namespaces. Re-exports from `effects/` (`installEffects`, shader effects), `perspective2d/` (`Scene2D`, `World`, `GameObject2D`, `Grid`), `cinema/` (`CameraDirector`, `ScreenShake`, `CameraFlash`, `DialogCameraCue`, `LetterboxFeature`, `ParallaxLayer`), `input/`, `ai/`, `flow/`, `debugger/`, `audio/` (`AudioFeature`), `persistence/` (`PersistenceFeature`, `SaveService`, `LocalStorageBackend`, `IndexedDBBackend`, `MemoryBackend`), `assets/` (`AssetFeature`), `particles/` (`ParticleFeature`), `net/` (`NetFeature`, `WebSocketTransport`, `LoopbackTransport`), `tilemap/` (`TilemapFeature`).
- `@toolcase/web-components` ^4.x — **required** UI toolkit. Framework-free `tc-*` Web Components for HUDs, menus, inventories, dialogs, settings, screens, minimaps. Every `HTMLFeature` composes these instead of hand-rolling markup.
- `@toolcase/base` ^4.x — peer of `phaser-plus` and `web-components`. Helpers and data structures (`Broadcast`, `ObjectPool`, etc.).
- `@toolcase/logging` ^4.x — peer of `phaser-plus`. Scoped loggers wired through `Engine`.
Expand Down Expand Up @@ -979,7 +979,7 @@ For enemy AI, dialog flow, scene state — use `phaser-plus`'s `StateMachine` an

### Cinema / camera

`phaser-plus/cinema` ships `CameraDirector`, `ScreenShake`, `CameraFlash`, `LetterboxFeature`, `ParallaxLayer`. Register as features in your scene; expose triggers via the bus (`cinema:shake`, `cinema:flash`).
`phaser-plus/cinema` ships `CameraDirector`, `ScreenShake`, `CameraFlash`, `DialogCameraCue`, `LetterboxFeature`, `ParallaxLayer`. Register as features in your scene; expose triggers via the bus (`cinema:shake`, `cinema:flash`). `DialogCameraCue` dims everything outside a supplied `DialogFrame` rectangle and optionally applies a vignette — call `open(frame, opts?)` / `close()` around dialogue sequences.

### Input

Expand Down Expand Up @@ -1011,6 +1011,30 @@ save.save(0, { score: this.score })

`resolve(class)` lazy-instantiates on first call. Use `bind(class, factory)` for constructors that need args, or `provide(class, instance)` to inject a prebuilt singleton.

### Audio

`AudioFeature` is a scene-lifetime `Feature` wrapping Phaser's `Sound` manager with named buses (`music`, `sfx`, `ui`, `ambience`), crossfade, per-voice SFX pools, spatial positioning, and ducking. Register it, then drive it entirely through bus events or direct calls (`play`, `playMusic`, `crossfade`, `duck`, `setVolume`). Three bus events are emitted: `AUDIO_MUSIC_START`, `AUDIO_MUSIC_END`, `AUDIO_BUS_CHANGE`.

### Persistence / save data

`PersistenceFeature` wraps a `SaveService` (game-lifetime) with an async slot API (`save`, `load`, `delete`, `list`, `has`) and emits `SAVE_DONE`, `LOAD_DONE`, `SAVE_DELETED` on the feature bus after each operation. Choose a backend at service binding time: `LocalStorageBackend` (synchronous, browser), `IndexedDBBackend` (async, larger payloads), or `MemoryBackend` (ephemeral / testing). Register `SaveService` in `services/` with the desired backend, then register `PersistenceFeature` in the scene that needs save/load.

### Asset loading (runtime bundles)

`AssetFeature` is a `Feature` for declarative, retry-aware asset loading at runtime (after the initial boot preload). Feed it an `AssetManifest` (named bundles of images, atlases, audio, bitmap fonts) and call `loadBundle(name)`. It emits `ASSET_PROGRESS` per-file, `ASSET_LOAD_COMPLETE` on bundle finish, and `ASSET_LOAD_ERROR` on failure. Useful for lazy-loading level art or DLC content mid-session without restarting the scene.

### Particles

`ParticleFeature` is a `Feature` that manages a registry of named `ParticlePreset` configurations wrapping Phaser's particle emitters. Register presets once in `onCreate`, then call `burst(preset, x, y)` for one-shot FX or `stream(preset, x, y)` for a continuous emitter (returns a `StreamHandle` with `stop()`). Events: `PARTICLE_BURST`, `PARTICLE_STREAM_START`, `PARTICLE_STREAM_STOP`. Presets can optionally attach a per-particle GLSL `Effect`.

### Networking

`NetFeature` drives a pluggable `Transport` (WebSocket or the in-process `LoopbackTransport` for local testing) with ping/pong RTT measurement, snapshot + delta entity sync (`syncEntity`, `interpolateEntity`), and optional client-side prediction (`predict`, `getPredicted`). Wire RTT/loss stats to the `NetPanel` debugger via `bindDebugger(dbg)`. Events: `NET_CONNECTED`, `NET_DISCONNECTED`, `NET_RTT_UPDATE`, `NET_ENTITY_STATE`. Swap the wire format by replacing `_sendMessage`/`_onMessage` with a `@toolcase/serializer` codec — the rest of the class is agnostic.

### Tilemaps

`TilemapFeature` is a `Feature` that loads Tiled (`.tmj`) or LDtk (`.ldtk`) maps, adds tilesets, renders layers, wires arcade-physics colliders via `buildColliders`, and generates a `TilemapNavMesh` for A\* pathfinding via `buildNavMesh({ walkable: [...] })`. Convert a Tiled object layer to an `ObjectLayer` with `toObjectLayer(key, layerName, poolKey)`, or read raw objects with `getObjects(layerName)`.

---

## Recipes
Expand Down
Loading
Loading