Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
b8390c3
feat(pbr-ibl): pbr-ibl working
krisnye May 15, 2026
97e57ea
feat(pbr): declarative model loading via Geometry + Model archetypes
krisnye May 15, 2026
6820736
feat(pbr): separate PbrMaterial from PbrPrimitive archetypes
krisnye May 15, 2026
844cfac
feat(graphics): instanced rendering via per-Model TRS transforms
krisnye May 15, 2026
04d3178
feat(graphics): GPU domain namespace for general WebGPU utilities
krisnye May 15, 2026
af4b989
feat(graphics): world-transform hierarchy + solar system sample
krisnye May 15, 2026
883c56d
feat(pbr): node-local vertex space + pbrNodeLocalMatrix (skinning prep)
krisnye May 15, 2026
0fd9dc8
refactor(graphics): eliminate src/pbr/ — fold into plugins/ and types/
krisnye May 15, 2026
aec74a1
feat(samples): cesium-milk-truck — multi-mesh glTF with node local tr…
krisnye May 15, 2026
5cae4bd
refactor(samples): remote asset URLs; replace cesium-milk-truck with …
krisnye May 15, 2026
0915569
refactor: rename data-graphics → data-gpu across packages, branch, an…
krisnye May 15, 2026
4822a00
feat(animation): generic ECS animation plugin + sample refactors
krisnye May 16, 2026
8c47a7b
feat(skinning): glTF skinning + animation, end-to-end
krisnye May 16, 2026
71e55fc
refactor(orbit-camera): consolidate per-sample orbit code into one pl…
krisnye May 16, 2026
bde48d2
docs(samples): add CLAUDE.md — no binary assets in repo
krisnye May 16, 2026
1ee6047
docs: NEXT_STEPS.md — parked roadmap items
krisnye May 16, 2026
924cba7
feat(samples): boids — full-GPU, no readback, indirect draw
krisnye May 20, 2026
7a37653
tune(boids): aquarium walls, fewer boids, visible flocks
krisnye May 20, 2026
4699fb6
tune(boids): noise-clumped seed, flow-field velocity, 2.5k count
krisnye May 20, 2026
86dac54
tune(boids): revert to toroidal wrap, drop aquarium walls
krisnye May 20, 2026
811639e
feat(boids): cursor scare — flocks flee from the mouse
krisnye May 20, 2026
95e5135
docs(rules): plugin-modelling — authored vs derived, concise format
krisnye May 25, 2026
7bed8de
docs(rules): plugin-modelling — domain-neutral, broader path match
krisnye May 25, 2026
45d5e9b
docs(rules): plugin-modelling — no wrapping in shape comments
krisnye May 25, 2026
92260b1
docs(rules): plugin-modelling — system format, query DSL, underscore …
krisnye May 25, 2026
9817257
refactor(data-gpu): feature-centric layout under graphics/, strip _ p…
krisnye May 26, 2026
5dd3504
feat(boids): ray-cast scare, denser flock, direction-hued colors
krisnye May 26, 2026
326015b
light and graphics refactor
krisnye May 26, 2026
ef46875
feat(picking): ray-pick Models via _worldBounds + linear scan
krisnye May 31, 2026
2fddd43
refactor(data-gpu): reorganize into scene/, rendering/, camera/orbit/…
krisnye Jun 1, 2026
0d45af7
refactor(data-gpu): graphics-plugin → core/core-plugin, export graphi…
krisnye Jun 1, 2026
a2ea390
refactor(data-gpu): split core (GPU runtime) from graphics (presentat…
krisnye Jun 1, 2026
ddc46ef
feat(physics): Phase A — GPU XPBD spheres vs static world + physics-d…
krisnye Jun 1, 2026
b110c90
feat(physics): Phase B — XPBD sphere-sphere collision (spheres stack)
krisnye Jun 1, 2026
3c2f5e1
feat(physics): Phase C — flag-gated collision events with CPU readback
krisnye Jun 1, 2026
90bc1ca
feat(physics): Phase C — query-only ephemeral particles, one-way coupled
krisnye Jun 1, 2026
732a073
feat(physics): Phase D1 — rigid orientation + cuboids (sphere/box, no…
krisnye Jun 1, 2026
bfe93be
feat(physics): Phase D2 — box-box SAT contacts (cuboids stack & tumble)
krisnye Jun 1, 2026
de4cca4
chore(physics-drop): update sample hint to reflect spheres, cuboids &…
krisnye Jun 1, 2026
7f285e5
feat(physics): LBVH broadphase — O(N log N) neighbour search on the GPU
krisnye Jun 1, 2026
16086d2
fix(physics): stability pass 1 — NaN-safe normalize + escape containment
krisnye Jun 2, 2026
f30fe4a
fix(physics): stability pass 2 — Small-Steps XPBD substepping
krisnye Jun 2, 2026
5ff3e56
fix(physics): stability pass 3 — under-relaxation + energy budget (cl…
krisnye Jun 2, 2026
879d068
fix(physics): damping is per-frame — rescale across substeps
krisnye Jun 2, 2026
a1174ab
feat(physics): S1 — Material table (solver rework foundation)
krisnye Jun 2, 2026
b789b1a
feat(physics): S2 — velocity-level restitution + friction pass
krisnye Jun 2, 2026
ee5690f
feat(physics): C1 — shared rigid-body data model + pluggable solver seam
krisnye Jun 2, 2026
fcc09e2
feat(physics): C2 — CPU sequential-XPBD solver + rigid-stack sample
krisnye Jun 2, 2026
ef50f5c
feat(physics): multi-point box-box manifold + rigid-stack sample adju…
krisnye Jun 2, 2026
43dcd8d
feat(physics): per-material colors in rigid-stack render
krisnye Jun 2, 2026
2e0b484
refactor(material): move materials into ECS registry (P1 of PBR unifi…
krisnye Jun 2, 2026
823e093
feat(material): materialGpu palette + texture-array builder (P2 of PB…
krisnye Jun 2, 2026
b20ba38
feat(render): unified PBR+IBL primitive pipeline + physics bridge → r…
krisnye Jun 2, 2026
6570dd2
feat(render): environment-map skybox in pbrRender
krisnye Jun 2, 2026
cd27d6b
feat(render): unify glTF + primitives under one pbrRender plugin (P4)
krisnye Jun 2, 2026
e7b84be
feat(physics): rolling + spinning friction; 6x6x6 rigid-stack
krisnye Jun 2, 2026
cff80b3
chore: remove physics-drop sample + shelved GPU XPBD solver
krisnye Jun 2, 2026
129b13d
feat(physics): true Coulomb friction in the position solve
krisnye Jun 2, 2026
d8b26f9
feat(physics): per-material compliance in the position solve
krisnye Jun 2, 2026
792226a
feat(physics): shape-aware rolling friction (spheres only)
krisnye Jun 2, 2026
addbdb0
feat(physics): sleeping islands — skip settled bodies, wake on contact
krisnye Jun 2, 2026
e9d06f2
fix(physics): stacks exploded on impact after sleeping landed
krisnye Jun 2, 2026
20b67e6
fix(physics): tame impact explosions on the rigid stack
krisnye Jun 2, 2026
6bd22e8
perf(physics): direct typed-array access in the solver gather/scatter
krisnye Jun 2, 2026
2f8851a
perf(render): allocation-free primitive instance packing
krisnye Jun 2, 2026
cb9e6a8
docs(rigid-stack): correct stale stack-gap comment (all-axis, not ver…
krisnye Jun 2, 2026
4bfe9b5
docs: hot-loop perf guidance (data-gpu CLAUDE.md) + /think skill
krisnye Jun 3, 2026
5631a8c
feat(core): frameTime resource — one clock, no per-system performance…
krisnye Jun 3, 2026
3ca25b5
feat(physics): StaticCollider archetype — floors/walls are real colli…
krisnye Jun 3, 2026
a515812
fix(physics): clip box-box incident face — stops ejection off static …
krisnye Jun 3, 2026
107cfd6
fix(physics): tame dense-pile impact blow-ups on static-box colliders
krisnye Jun 3, 2026
7cd3c97
feat(samples): Rapier solver plugin + side-by-side solver comparison
krisnye Jun 3, 2026
3cc01e6
fix(ecs,core): run simulations headless (Node) with no rendering
krisnye Jun 3, 2026
81784d6
feat(physics): headless solver benchmark harness + baselines
krisnye Jun 3, 2026
4a5d60e
feat(samples): Jolt solver plugin — three-way side-by-side + benchmark
krisnye Jun 3, 2026
38722f0
refactor: move Rapier + Jolt solver plugins into @adobe/data-gpu
krisnye Jun 3, 2026
bbddb0a
chore(data-gpu): sideEffects:false + engines as optional peer deps
krisnye Jun 3, 2026
c151321
chore(data-gpu): engines as regular deps + sideEffects:false (tree-sh…
krisnye Jun 3, 2026
1da5e6c
perf(physics): engine solvers sync only un-mirrored bodies (tag + exc…
krisnye Jun 4, 2026
850396d
docs(data-gpu): ECS-traversal perf playbook in CLAUDE.md + residual note
krisnye Jun 4, 2026
3591964
refactor(physics): remove CPU-XPBD solver; Jolt is the default, Rapie…
krisnye Jun 4, 2026
24ec0f3
feat(physics): fixed-timestep clock — decouple sim rate from render rate
krisnye Jun 4, 2026
e6900ea
feat(render): render-rate interpolation of fixed-step physics poses
krisnye Jun 4, 2026
ef9746f
refactor(render): fold interpolation into physicsRenderBridge
krisnye Jun 4, 2026
0e47760
feat(physics): kinematic body support in both solvers
krisnye Jun 4, 2026
5797704
feat(physics): capsule collider shape + render
krisnye Jun 4, 2026
fd9e6aa
feat(shape): convex-hull triangulation utility (for hull collider ren…
krisnye Jun 5, 2026
0a46b37
feat(physics): convex-hull collider (authored point cloud)
krisnye Jun 5, 2026
747929a
feat(physics): static triangle-mesh collider
krisnye Jun 5, 2026
8eb7fdc
chore(samples): zone the rigid-stack bin so the bar doesn't bulldoze …
krisnye Jun 5, 2026
634292c
feat(physics): auto-generate model colliders from render geometry
krisnye Jun 5, 2026
24c62bd
feat(physics): defer hull/mesh mirroring until generated; demo a mode…
krisnye Jun 5, 2026
32797cf
feat(physics): joints/constraints (fixed, point, hinge) + roadmap README
krisnye Jun 5, 2026
df95a66
feat(physics): cone (swing-twist) joint limits + apply authored veloc…
krisnye Jun 5, 2026
73a5c02
docs(physics): note deterministic-headless-joint-test follow-up in ro…
krisnye Jun 5, 2026
00301ae
feat(physics): per-bone capsule fitting for skinned meshes (ragdoll s…
krisnye Jun 5, 2026
f20f55b
feat(physics): bone-collider tracking + skinned humanoid sample (ragd…
krisnye Jun 5, 2026
6f3d335
docs(physics): check off per-bone colliders in the roadmap
krisnye Jun 5, 2026
5cb65d3
feat(physics): minimal collisionGroup + put the ragdoll sample on a s…
krisnye Jun 5, 2026
4320033
feat(physics): ragdoll controller — kinematic→dynamic flip, joints, r…
krisnye Jun 5, 2026
d256b69
docs(physics): check off the ragdoll controller in the roadmap
krisnye Jun 5, 2026
f5391d4
feat(physics): ragdoll uses cone joints with real anatomical limits (…
krisnye Jun 5, 2026
effb9df
refactor(samples): ragdoll side-by-side on both solvers; extract ragd…
krisnye Jun 5, 2026
dae4c9f
feat(physics): Jolt-native Ragdoll backend (joltRagdoll), side-by-sid…
krisnye Jun 5, 2026
0b47347
docs(physics): roadmap notes both ragdoll backends (Jolt-native + gen…
krisnye Jun 5, 2026
dc8c476
Merge remote-tracking branch 'origin/main' into krisnye/data-gpu
krisnye Jun 9, 2026
c3e8a39
fix(p2p-tictactoe): type-correct bootstrap containers against full da…
krisnye Jun 9, 2026
894d62b
chore: bump version to 0.9.68
krisnye Jun 9, 2026
68b5153
refactor(data-lit): expose full database on DatabaseElement; drop all…
krisnye Jun 9, 2026
32410e8
Merge remote-tracking branch 'origin/main' into krisnye/data-gpu
krisnye Jun 10, 2026
43a5397
bumped
krisnye Jun 10, 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
60 changes: 60 additions & 0 deletions .claude/rules/archetypes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
description: Authoring rules for ECS systems that iterate archetype rows.
globs: "**/*.ts"
---

# Archetypes — iteration rules

## Express selection in the query, not the loop

`queryArchetypes(include, { exclude })` accepts both required and excluded
component lists. Use them. Don't query a wider set and then skip rows or
archetypes with an `if`.

```ts
// ❌ post-filter
for (const arch of db.store.queryArchetypes(["position", "rotation"])) {
if (arch.components.has("_worldMatrix")) continue;
...
}

// ✅ declarative
for (const arch of db.store.queryArchetypes(
["position", "rotation"],
{ exclude: ["_worldMatrix"] },
)) {
...
}
```

## When every row migrates out, iterate tail → head

Archetypes are densely packed. Removing or migrating a row that isn't the
last one triggers a hole-fill: the tail row is moved into the gap.
Iterating `0 → rowCount-1` while migrating every row pays this cost on
every iteration. Iterating `rowCount-1 → 0` means each removal is from
the tail — no shift, indices ahead of the cursor stay valid.

```ts
// ❌ shifts the tail into every hole, and the snapshot allocation is
// only there to survive the shifts.
const ids = [...];
for (let i = 0; i < arch.rowCount; i++) ids[i] = arch.columns.id.get(i);
for (const id of ids) db.store.update(id, { _worldMatrix: Mat4x4.identity });

// ✅ no shifts, no allocation
for (let i = arch.rowCount - 1; i >= 0; i--) {
db.store.update(arch.columns.id.get(i), { _worldMatrix: Mat4x4.identity });
}
```

If only *some* rows migrate (filter inside the loop), snapshot the ids
you'll touch — forward iteration is fine because rows you don't touch
stay put.

## Don't snapshot what the query already filters

A snapshot of "all entity ids in this archetype right now" is only
needed when forward iteration would invalidate. Reverse iteration
removes the need; an `exclude` clause removes the need to look at rows
that don't qualify in the first place.
57 changes: 57 additions & 0 deletions .claude/rules/namespace.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,60 @@ as both type and namespace (`LogLevel.is(x)`, `LogLevel.values`).
avoid cycling through `public.ts`.
- Add `is` / `values` / per-member descriptors only when an external
consumer actually needs them — not preemptively.

## Multi-declaration files (when lumping is OK)

Default stays one type / one helper per file. Two named exceptions:

- **`<type>-functions.ts`** — sibling functions operating on one owned
type. Lives in that type's folder. Use when functions are small enough
that adjacency in one editor pane beats one-file-per-function.
- **`<format>-schema.ts`** — TypeScript projection of a borrowed data
format (file format, wire protocol, third-party API). Plain `export
interface` only — no namespace, no helpers. Lives next to the parser/
emitter, not in a type folder.

Never `*-types.ts`. "Types" is a meta-word the `.ts` extension already
implies; the name has to predict the contents. If you can't beat
`-types`, the file isn't a real lump — split per type, or inline at use
site.

## Domain namespaces (for types you don't own)

When utility functions operate on a platform or third-party type (e.g.
`GPUDevice`, `GPUTexture`), use a **domain namespace** instead: a folder
named after the concept, with a namespace export but *no type re-export*.

```
src/gpu/
gpu.ts # `export * as GPU from "./public.js"` — no type alias
public.ts # re-exports every public helper
<helper>.ts # one declaration per file
```

```ts
// gpu/gpu.ts
export * as GPU from "./public.js";

// gpu/public.ts
export { createCubemap } from "./create-cubemap.js";
export { cubeFaceView } from "./cube-face-view.js";
```

Consumers import the namespace and get discoverability without shadowing
the platform type:

```ts
import { GPU } from "@adobe/data-graphics";
GPU.createCubemap(device, 256, "rgba16float");
```

**Name by purpose, not by the external type.** `GPU`, not `GPUDevice`.

**Tree-shaking still works**: bundlers (Rollup/Rolldown) trace static
property accesses (`GPU.createCubemap`) and exclude unused helpers.
Dynamic access (`GPU[name]`) defeats this — avoid it.

**When NOT to use a domain namespace**: if a utility is only useful as
an internal implementation detail of a specific plugin (not callable by
consumers), keep it private to that plugin's folder instead.
170 changes: 170 additions & 0 deletions .claude/rules/plugin-modelling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
paths:
- '**/*plugin*'
- '**/*plugin*/**'
---

# Plugin modelling — model vs. system, authored vs. derived

Each plugin has two orthogonal axes:

1. **Authored** state — the user inserts / sets it. Designed by the human
architect.
2. **Derived** state — a system produces it from other state. Implementation;
owned by the AI.

The human's mental model of a project is the union of every plugin's
**authored surface**. Systems and derived state are how the world updates
and renders — read their interface contract, not their innards.

## Two tiers of model plugins

- **Core** — the primary record types every consumer reads. Small, fixed,
shared.
- **Authoring abstractions** — higher-level intents (orbit, animation,
procedural shape, particle emitter, constraint, …) that systems expand
into core state. Open-ended; each project picks what it needs.

A plugin almost never holds both authored and derived data the user cares
about. If it appears to, the derived data is the *implementation* of the
authoring abstraction.

## File naming

Each file whose primary export is a `Database.Plugin` (created or
combined) ends in `-plugin.ts` — e.g. `node-plugin.ts`, `model-plugin.ts`,
`pbr-core-plugin.ts`. The suffix is for discovery: cursor rules,
codebase grep, and "find all plugins" tooling match `**/*-plugin.ts`
without inspecting contents.

The exported constant's name depends on whether the folder also hosts a
type namespace:

- **Pure-plugin folder** (no owned type) → export the concept name
itself, lowercase. `node-plugin.ts` exports `node`. Import sites read
`import { node } from "..."; Database.Plugin.combine(node, ...)`.
- **Type + plugin folder** (folder hosts both an owned type and its
plugin) → the plugin file exports `plugin`, re-exported through the
type's namespace. `camera-plugin.ts` exports `plugin`; `camera/public.ts`
re-exports it; consumers reach it as `Camera.plugin`. One import gives
both the type and the plugin.

Files with helper exports, value types, schemas, or shaders do **not**
take the suffix — only plugin files.

## Resources and archetypes as types

Prefer **one resource per plugin**, of an owned type that the folder also
hosts. `light/` has one `light` resource of type `Light`; `orbit/` has
one `orbit` resource of type `Orbit`. Splitting a single coherent
concept into siblings (`lightDirection`, `lightColor`, `ambientStrength`)
duplicates the concept's identity at every read site.

For each **archetype** the plugin declares, add a TypeScript type with
the same name describing one row's authored shape. `Node` is the type
for the `Node` archetype's row. The components stay separate per
typed-buffer-column convention; the type names the bundle so consumers
can declare `const node: Node` after a read.

Both cases follow the same folder shape:

```
<concept>/
<concept>.ts # type + namespace
public.ts # re-exports plugin (and any helpers)
<concept>-plugin.ts # exports `plugin`
```

Consumers reach the plugin as `Concept.plugin` and the type as
`Concept`. Pure-plugin folders (no owned type) still export their
plugin under a lowercase concept name — see "File naming" below.

**Exceptions:**
- Plugins whose resources have genuinely independent lifecycles
(`graphics` — `device`, `canvas`, `commandEncoder`, each set at
different times) keep them as separate resources. Consolidating
would force any one write to replace the whole struct.
- Ephemeral implementation archetypes (e.g. `_PbrPrimitive`,
`_VisibleMaterial`) don't need a value type — consumers iterate
them by archetype, never construct one.

## Shared conventions

- **Uppercase identifier** = archetype. **Lowercase** = component or
resource. Disambiguated by where declared (archetype vs. component vs.
resource section of the data plugin).
- **Entity references** are typed `EntityId`; field names drop the `Ref`
suffix — the type carries the signal.
- **Implementation prefixes** (`pbr`, `ibl`, …) belong in code where they
disambiguate cross-plugin reuse. Drop them in the conceptual view.
- **`_` prefix** = ephemeral / derived / not part of the data model.
Applies to **components, archetypes, and resources** in code — the
prefix makes "this is system-owned, not user-authored" visible at every
read site. The human ignores it when modelling; system-graph views
still display it. Does **not** apply to plugin names, system names, or
transactions — those are grouped by feature folder, and the folder
already communicates whether a plugin is authored surface or
implementation.
- **Inline `: Type`** only on `_`-prefixed names. Persistent items get
their type from the data plugin where they're declared.
- **Components use JSON schemas**, resources use `{ default: X as T }`.
Schemas (e.g. `Entity.schema`, `Mat4x4.schema`, `Boolean.schema`,
`{ type: "string" }`) enable typed-buffer column storage — the right
choice when a value is stored once per entity. Resources are a single
slot per database, so the runtime cost of typed storage isn't worth
it; the default-pattern is the convention there. Use the default
pattern on components *only* for runtime-only objects (GPU buffers,
bind groups, closures, complex JS objects with no schema).

## Model plugin format

Authored surface only — no derived state, no systems.

```
pluginName
components
fieldName: Type
...
resources
fieldName: Type
...
archetypes
ArchetypeName: [field, field, ...OtherArchetype]
```

- `...Other` composes another archetype's component list.
- Shape comments (`// { … }`) stay on the same line; do not wrap.

## System plugin format

```
systemName // high-level summary
query: ArchetypeExpr [, ArchetypeExpr ...] // omit if resource-only
read:
name // persistent — type from data plugin
_name: Type // ephemeral — type inline
write:
name // writes a component / resource
_Archetype // creates new entities of this archetype
// free-text side effect // draw calls, network sends, …
```

- `read:` and `write:` are outlined one-per-line.
- A system can have multiple independent queries; comma-separate them on
the `query:` line.

## Query DSL

- `Archetype` — entities matching the archetype.
- `+component` — require this additional component.
- `-component` — exclude entities that have it.
- `Archetype+a-b+c` — left-to-right chain of `+` / `-`.
- `Q1, Q2, ...` — multiple independent queries the system reads from.

## Designing or discussing a plugin

1. Write the authored surface (model format) **first**.
2. Then each system as its own interface card with `query` / `read` /
`write`.
3. The graph of `write:` → `read:` edges is the implementation; the union
of authored surfaces is the human's mental model.
Loading