@jgengine/assets 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -3
- package/README.md +103 -50
- package/dist/aliases.d.ts +5 -0
- package/dist/aliases.js +28 -9
- package/dist/catalogs/build.js +9 -0
- package/dist/catalogs/buildSprites.d.ts +10 -0
- package/dist/catalogs/buildSprites.js +15 -0
- package/dist/catalogs/starter.d.ts +5 -0
- package/dist/catalogs/starter.js +10 -1
- package/dist/cli/paths.d.ts +9 -1
- package/dist/cli/paths.js +11 -1
- package/dist/cli/pull.d.ts +12 -0
- package/dist/cli/pull.js +210 -15
- package/dist/download.d.ts +46 -3
- package/dist/download.js +158 -9
- package/dist/find.d.ts +20 -1
- package/dist/find.js +52 -2
- package/dist/generated/index.js +18 -6
- package/dist/generated/kaykit-adventurers.json +674 -0
- package/dist/generated/kaykit-city-builder.json +863 -0
- package/dist/generated/kaykit-dungeon.json +4265 -0
- package/dist/generated/kaykit-furniture.json +1115 -0
- package/dist/generated/kaykit-skeletons.json +410 -0
- package/dist/generated/kaykit-space-base.json +1199 -0
- package/dist/generated/quaternius-medieval-village.json +3698 -0
- package/dist/generated/quaternius-modular-scifi.json +4013 -0
- package/dist/generated/quaternius-stylized-nature.json +1430 -0
- package/dist/generated-sprites/index.d.ts +5 -0
- package/dist/generated-sprites/index.js +4 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +9 -2
- package/dist/indexGen.js +7 -5
- package/dist/manifest.d.ts +12 -1
- package/dist/materials.d.ts +61 -0
- package/dist/materials.js +98 -0
- package/dist/packs/starter.d.ts +46 -0
- package/dist/packs/starter.js +248 -0
- package/dist/provisioning.d.ts +88 -0
- package/dist/provisioning.js +74 -0
- package/dist/registry-catalog.json +40 -0
- package/dist/snippet.d.ts +4 -0
- package/dist/snippet.js +27 -0
- package/dist/sources/ambientcg.d.ts +5 -0
- package/dist/sources/ambientcg.js +64 -0
- package/dist/sources/gameicons.d.ts +10 -0
- package/dist/sources/gameicons.js +21 -0
- package/dist/sources/index.d.ts +9 -2
- package/dist/sources/index.js +11 -3
- package/dist/sources/kaykit.js +55 -6
- package/dist/sources/quaternius.js +60 -9
- package/dist/spriteIndexGen.d.ts +17 -0
- package/dist/spriteIndexGen.js +108 -0
- package/dist/verify.d.ts +1 -0
- package/dist/verify.js +38 -1
- package/package.json +4 -4
- package/skills/jgengine/SKILL.md +51 -0
- package/skills/jgengine/api.md +434 -0
- package/skills/jgengine/capabilities.md +29 -0
- package/skills/jgengine/reference-cartridge.md +72 -0
- package/skills/jgengine-assets/SKILL.md +41 -0
- package/skills/jgengine-assets/api.md +273 -0
- package/skills/jgengine-assets/capabilities.md +13 -0
- package/dist/generated/kenney-blaster.json +0 -362
- package/dist/generated/kenney-nature.json +0 -3292
- package/dist/generated/kenney-space.json +0 -1532
- package/dist/sources/kenney.d.ts +0 -2
- package/dist/sources/kenney.js +0 -36
- package/llms.txt +0 -997
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
The lockstep game SDK set is `@jgengine/{core,react,ws,node,sql,convex,shell,editor,assets}`
|
|
4
|
+
(this file’s 0.x.y releases). The CLI package `jgengine` and `@jgengine/github` use
|
|
5
|
+
**separate** version lines and may lag. Format follows [Keep a Changelog](https://keepachangelog.com);
|
|
5
6
|
each release **leads with a Migrate block** — the concrete steps to move a game
|
|
6
7
|
from the previous version onto the new APIs — because the point of a bump is to
|
|
7
8
|
let consumers pick up the better stuff, not just to list what moved.
|
|
@@ -11,7 +12,37 @@ Agents building on the published SDK can also read this programmatically:
|
|
|
11
12
|
same data as typed values, so an updater can diff its installed version against
|
|
12
13
|
the latest and surface the migration steps.
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## 0.11.0
|
|
16
|
+
|
|
17
|
+
### Migrate
|
|
18
|
+
|
|
19
|
+
- **Nothing required** — classic `loop.onTick` fan-out still runs after systems. Prefer moving per-frame work into `defineGame({ systems: [...] })` with `defineSystem` (`@jgengine/core/game/defineSystem`); install a system with `feature: "quest"` (etc.) instead of a redundant `features` flag. See skill `jgengine` → [reference-systems.md](.claude/skills/jgengine/reference-systems.md).
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Composable game systems** (#842) — `defineSystem` / `compileSystemSchedule` / `composeGameLoop` / `installSystems`. `defineGame({ systems })` is the single authoring path: fixed / frame / interval / event-only / manual ticks, multi-subscribe channels, deterministic stage order + optional `before`/`after`/`dependsOn`, system-owned save / replicate / reset / dispose. Installing a system activates its `feature` without a separate flag. `GameLoop` gains optional `onReset` / `onDispose`. `ctx.game.registerSave` / `registerReplicate` for system modules. Adopters: tower-guard, claudecraft (runtime without a giant tick fan-out).
|
|
24
|
+
|
|
25
|
+
## 0.10.0
|
|
26
|
+
|
|
27
|
+
### Migrate
|
|
28
|
+
|
|
29
|
+
- **Nothing to change for existing games** — the shared mobile-composition system (viewport allocation, region collision, `--jg-*` viewport vars) is wired automatically by the shell for every game; `HudPanel`s inside a `HudCanvas` register themselves. Legacy `orientation: "landscape" | "portrait"` keeps its advisory dismissible-hint behavior unchanged.
|
|
30
|
+
- **Opt a driving/landscape game into the strict gate**: replace the advisory `orientation` with the contract form — `orientation: { mobile: "landscape-required" }` (rules: `any` · `portrait`/`landscape` advisory · `portrait-required`/`landscape-required` · `unsupported`). In portrait the engine now shows a polished `RotateDeviceScreen`, suppresses input, freezes the sim, and unmounts the HUD/controls until the device is landscape.
|
|
31
|
+
- **Declare HUD intent** where useful: `HudPanel` gains `priority` (`critical`/`secondary`/`tertiary`), `mobileBehavior` (`hidden` unmounts on phones, `transient` softens collision, …), `allowOverlapWith`, and `collisionGroup`. Optional — omitting them keeps prior behavior.
|
|
32
|
+
- **Mobile validation now also fails on overlap**: `bun run shoot <game> --device mobile|mobile-landscape|both` exits non-zero on forbidden inter-element collisions (not just viewport overflow), naming both regions.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Shared mobile layout composition — `@jgengine/core/ui/gameLayout` (pure geometry: `resolveLayoutMode`, `intersects`, `overlapArea`, `detectLayoutCollisions`, `computeGameplayRect`, `LayoutRegion`, `GameViewportLayout`) and `@jgengine/core/ui/orientation` (`resolveOrientationRequirement`, `orientationGateActive`, `MobileOrientationRule`). `@jgengine/react` adds `GameViewportProvider` (mounted by the shell), hooks (`useGameViewportLayout`, `useGameLayoutMode`, `useGameOrientation`, `useReservedControlZones`, `useLayoutCollisions`, `useRegisterLayoutRegion`), and a headless `RotateDeviceScreen`. The provider tracks `window.visualViewport`, publishes `--jg-viewport-*` / `--jg-visual-viewport-*` / `--jg-safe-*` CSS vars, resolves the explicit layout mode, and detects forbidden region overlaps (dev `data-jg-layout-collision` + console diagnostic).
|
|
37
|
+
- Mandatory orientation contract — `PlayableGame.orientation` accepts `{ mobile: <rule> }`; `landscape-required`/`portrait-required` render an engine-owned rotate gate that blocks gameplay (input suppressed, simulation frozen, HUD/controls unmounted), self-correcting when the device is turned. Reduced-motion and safe-area respected; `visualViewport`-sized for mobile Safari/PWA.
|
|
38
|
+
- Reserved touch-control zones — the touch dock registers its joystick / action-cluster / utility rectangles (runtime-measured) as `control` regions, so HUD placement and collision validation know exactly where controls sit.
|
|
39
|
+
- HUD placement metadata — `HudPanel` gains `priority`, `mobileBehavior`, `allowOverlapWith`, `collisionGroup`, and registers as a collision-tracked layout region.
|
|
40
|
+
- Mobile-landscape shoot device — `bun run shoot --device mobile-landscape` (844×390) plus collision-gate reads that fail mobile validation on forbidden overlaps.
|
|
41
|
+
- Canyon Chase migrated as the reference landscape-required game — declares `orientation: { mobile: "landscape-required" }`, composes its HUD through coordinated `HudPanel` regions (pursuit distance critical, border secondary, survey map hidden on mobile, radio transient) instead of independent fixed corners.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- First-person projectile tracers now originate from the weapon muzzle instead of the camera/eye centerline. Hit detection is unchanged (still crosshair-accurate) — only the drawn tracer's start point moved to the viewmodel muzzle, which tracks full aim yaw and pitch. Non-viewmodel games and enemy tracers are unaffected.
|
|
15
46
|
|
|
16
47
|
## 0.9.0
|
|
17
48
|
|
package/README.md
CHANGED
|
@@ -1,37 +1,45 @@
|
|
|
1
1
|
# @jgengine/assets
|
|
2
2
|
|
|
3
|
-
A self-generating, license-verified index of thousands of CC0 3D models
|
|
3
|
+
A self-generating, license-verified index of thousands of CC0 3D models, PBR materials, **and 2D sprite/icon packs** G�� hosted at **zero cost**. No GLB, texture, or sprite bytes ship in the npm tarball; every byte comes from infrastructure you don't pay for:
|
|
4
4
|
|
|
5
5
|
| What | Where it lives | Whose bandwidth |
|
|
6
6
|
|------|----------------|-----------------|
|
|
7
|
-
| Pack GLBs (
|
|
7
|
+
| Pack GLBs (Quaternius, KayKitGǪ) | Fetched at `pull` time from the provider's CDN | Provider's |
|
|
8
|
+
| PBR materials (ambientCG) | Fetched at `pull` time (provider or the `packs` release mirror) | Provider's / GitHub's |
|
|
9
|
+
| Sprite/icon packs (game-icons.net) | Fetched at `pull` time (provider or the `packs` release mirror) | Provider's / GitHub's |
|
|
8
10
|
| The generated index (JSON) | Inside this npm package | npm (KB) |
|
|
9
11
|
| Your one-off models | `packages/assets/local/`, served via jsDelivr-over-GitHub | GitHub + jsDelivr |
|
|
10
|
-
| A consumer's downloaded bytes | Their `public/models/` (gitignored) | The consumer's |
|
|
12
|
+
| A consumer's downloaded bytes | Their `public/models/` + `public/materials/` + `public/sprites/` (gitignored) | The consumer's |
|
|
11
13
|
|
|
12
14
|
## Layers
|
|
13
15
|
|
|
14
|
-
1. **Sources** (`src/sources/*.ts`)
|
|
15
|
-
2. **Generated index** (`src/generated/*.json`)
|
|
16
|
-
3. **Aliases** (`src/aliases.ts`)
|
|
17
|
-
4. **Singles** (`src/singles.json`)
|
|
16
|
+
1. **Sources** (`src/sources/*.ts`) G�� one entry per downloadable pack. Every entry carries required `license` + `author` and a `download` that is either a pinned `{ url, sha256? }` or a `{ scrape }` marker.
|
|
17
|
+
2. **Generated index** (`src/generated/*.json`) G�� machine-produced from the real `.glb` filenames after a pack is extracted. Never hand-typed. Committed JSON; bytes are not.
|
|
18
|
+
3. **Aliases** (`src/aliases.ts`) G�� hand-authored semantic keys (`nature/tree_pine G�� quaternius-stylized-nature/Pine_1`).
|
|
19
|
+
4. **Singles** (`src/singles.json`) G�� long-tail one-offs (per-model `url` + `license`).
|
|
20
|
+
5. **Materials** (`src/sources/ambientcg.ts` + `src/materials.ts`) G�� `kind: "material"` sources, one CC0 PBR material each (hundreds of ambientCG materials: grass, rock, wood, brick, metal, fabricGǪ). `pull` normalizes the maps to fixed filenames, so `buildMaterialCatalog({ basePath })` resolves ids and `material/GǪ` aliases to map URLs with no generated index at all.
|
|
21
|
+
6. **Sprites** (`src/sources/gameicons.ts` + `src/spriteIndexGen.ts`) G�� `kind: "sprite"` sources, packs of individual SVG/PNG icon and UI files (game-icons.net's ~4,000 CC BY 3.0 icons as one mirrored repo). Same shape as models G�� `reindex-sprites` discovers real files after a pull and writes `src/generated-sprites/*.json`, resolved through `buildSpriteCatalog({ basePath })`.
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
Model entries collapse into the core `AssetCatalog` via `buildCatalog({ basePath })`; materials resolve through `buildMaterialCatalog({ basePath })`; sprites/icons resolve through `buildSpriteCatalog({ basePath })`.
|
|
20
24
|
|
|
21
|
-
## `add`
|
|
25
|
+
## `add` G�� one command for anything
|
|
22
26
|
|
|
23
|
-
`assets add <query>` is the front door. It fuzzy-searches **every** catalog at once
|
|
27
|
+
`assets add <query>` is the front door. It fuzzy-searches **every** catalog at once G�� 3D models, whole packs, PBR materials, sprite/icon packs, HUD components (the shadcn registry), and `game-icon` glyphs G�� then does the fetch and prints the exact copy-paste wiring. One mental model instead of six.
|
|
24
28
|
|
|
25
29
|
```bash
|
|
26
|
-
assets add astronaut --dir ../../apps/dev/public # model
|
|
27
|
-
assets add nature --dir ../../apps/dev/public # whole pack
|
|
28
|
-
assets add
|
|
29
|
-
assets add
|
|
30
|
+
assets add astronaut --dir ../../apps/dev/public # model G�� pull + reindex + print the assets.ts snippet
|
|
31
|
+
assets add nature --dir ../../apps/dev/public # whole pack G�� pull + reindex + how to wire an id
|
|
32
|
+
assets add grass --kind material --dir ../../apps/dev/public # PBR material G�� pull maps + resolve snippet
|
|
33
|
+
assets add "game icons" --dir ../../apps/dev/public # sprite pack G�� pull + reindex-sprites + resolve snippet
|
|
34
|
+
assets add "mana bar" # HUD component G�� the `npx shadcn add` cmd + <VitalBar/> usage
|
|
35
|
+
assets add sword # icon G�� the game-icon name to drop in a slot
|
|
30
36
|
```
|
|
31
37
|
|
|
32
38
|
- A **model / pack** match is fully automated: if the pack isn't already in `<dir>/models/`, it's pulled and extracted, then `reindex` runs so the id is addressable, then the `buildCatalog` + model-seam snippet is printed.
|
|
33
|
-
- A **
|
|
34
|
-
-
|
|
39
|
+
- A **material** match pulls the maps into `<dir>/materials/<id>/` with normalized names (`color.jpg`, `normal.jpg`, `roughness.jpg`, `ao.jpg`, `displacement.jpg`) and prints the `buildMaterialCatalog` resolve snippet G�� no reindex needed, the material catalog is fully static.
|
|
40
|
+
- A **sprite / spritePack** match works like model/pack: if the pack isn't already in `<dir>/sprites/`, it's pulled and extracted, then `reindex-sprites` runs so individual SVG/PNG ids are addressable, then the `buildSpriteCatalog` snippet is printed.
|
|
41
|
+
- A **component / icon** match prints the one-liner to run and the import + usage G�� no bytes to pull.
|
|
42
|
+
- Ambiguous query? `add` lists the top matches across kinds; narrow with `--kind model|pack|material|component|icon|sprite|spritePack` or a more specific term. `--json` emits the ranked matches for scripting.
|
|
35
43
|
|
|
36
44
|
The same ranking is available programmatically: `import { findAssets } from "@jgengine/assets"`.
|
|
37
45
|
|
|
@@ -39,98 +47,143 @@ The same ranking is available programmatically: `import { findAssets } from "@jg
|
|
|
39
47
|
|
|
40
48
|
```
|
|
41
49
|
assets add <query> [--kind <k>] [--dir <dir>] [--mirror <baseUrl>] [--json]
|
|
42
|
-
# unified import: models, packs, components, icons
|
|
43
|
-
assets list [--category <c>] [--source <s>]
|
|
44
|
-
|
|
50
|
+
# unified import: models, packs, materials, sprites, components, icons
|
|
51
|
+
assets list [--category <c>] [--source <s>] [--kind material|sprite|spritePack]
|
|
52
|
+
# browse the generated model/sprite index, or the material/sprite-pack catalog
|
|
53
|
+
assets search <term> # grep the model index
|
|
45
54
|
assets pull <source-id> [--dir public] [--mirror <baseUrl>] [--offline]
|
|
46
|
-
# download + extract a
|
|
55
|
+
# download + extract a pack into <dir>/models|materials|sprites/<source>/
|
|
47
56
|
assets register <path|url> --category <c> --license <l> [--author <a>]
|
|
48
57
|
# register a one-off single into the shipped index
|
|
49
|
-
assets reindex [public/models] # regenerate generated/*.json from pulled packs
|
|
58
|
+
assets reindex [public/models] # regenerate generated/*.json from pulled model packs
|
|
59
|
+
assets reindex-sprites [public/sprites] # regenerate generated-sprites/*.json from pulled sprite packs
|
|
50
60
|
assets verify # license + alias-integrity gate
|
|
61
|
+
assets provenance <id> [<id>...] # resolve each id's owner (committed/provisioned/dangling); non-zero on any dangling
|
|
51
62
|
```
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
### Reference integrity
|
|
65
|
+
|
|
66
|
+
Every logical asset id resolves to a declared **owner** through the provisioning contract (`resolveProvenance`): `committed` (a single's shipped URL — resolves on a clean clone with no pull), `provisioned` (a pack index entry whose GLB `assets pull <source>` fetches into the served dir), or `dangling` (nothing owns it, so it can never resolve). `validateAssetReferences(refs)` turns a set of `{ consumer, id }` references into a clean-clone gate: it fails on any dangling reference — naming the consumer, the logical id, and the null resolved path — and returns the exact `assets pull <source>` steps the checkout still needs so provisioning stays deterministic and `ensure-ready`-driven. It resolves references structurally against the catalog rather than grepping source text.
|
|
67
|
+
|
|
68
|
+
At runtime the shell validates a model fetch *before* parsing (`classifyAssetResponse` in `@jgengine/core/scene/assetDiagnostics`): it checks HTTP status, content type, and the GLB/glTF signature, then reports `missing`, `html` (a dev-server fallback page served in place of the file), `corrupt`, or `unsupported` with the URL — replacing the opaque `Unexpected token <` GLTF parse error the old path produced when a model was absent.
|
|
69
|
+
|
|
70
|
+
Run in-repo with `bun run --cwd packages/assets src/cli/pull.ts <verb> GǪ`. (`add <path|url> --license GǪ` still works as an alias for `register`.)
|
|
54
71
|
|
|
55
72
|
### Mirror fallback and offline pulls
|
|
56
73
|
|
|
57
74
|
`pull` never has just one path to the bytes. For a given `source-id` it tries, in order, until one succeeds:
|
|
58
75
|
|
|
59
|
-
1. **Mirror base override**
|
|
60
|
-
2. **The default GitHub-release mirror**
|
|
61
|
-
3. **The primary provider path**
|
|
62
|
-
4. **The pack's own `mirror`**
|
|
76
|
+
1. **Mirror base override** G�� `--mirror <baseUrl>` (or the `JGENGINE_ASSETS_MIRROR` env var if `--mirror` is not passed) G�� the archive is expected at `<baseUrl>/<provider>/<source-id>.zip`, e.g. `https://my-mirror.example.com/quaternius/quaternius-stylized-nature.zip`.
|
|
77
|
+
2. **The default GitHub-release mirror** G�� `https://github.com/Noisemaker111/jgengine/releases/download/packs/<provider>-<source-id>.zip`, on this repo's own rolling `packs` release (no separate assets repo). github.com is reachable from every cloud sandbox with no network-policy change, so zero-setup sessions still pull. `.github/workflows/mirror-assets.yml` (weekly cron + manual dispatch) keeps the release in sync with `src/sources/*.ts` automatically G�� adding a catalog entry is the whole publishing step, no manual upload. Skip this hop with `JGENGINE_ASSETS_NO_DEFAULT_MIRROR=1`. Model/sprite pack failures fail the job; individual ambientCG material 404s are soft (logged, non-fatal).
|
|
78
|
+
3. **The primary provider path** G�� pinned `{ url, sha256? }` when stable: **KayKit** G�� `github.com/KayKit-Game-Assets/*/archive/GǪ/main.zip`; **Quaternius** free Standard packs G�� OpenGameArt direct zips (site pages JS-gate). Else scrape. `extractGlbs` packs co-located `.gltf`+`.bin` into `.glb`.
|
|
79
|
+
4. **The pack's own `mirror`** G�� an optional direct archive URL set on the `AssetSource` entry itself (`src/sources/*.ts`), tried as a last resort.
|
|
63
80
|
|
|
64
|
-
If every attempt fails, `pull` throws one aggregated error naming every URL it tried and why each one failed. Whenever the source's `download` is pinned with a `sha256`, the downloaded bytes are hashed and checked against it **no matter which path supplied them**
|
|
81
|
+
If every attempt fails, `pull` throws one aggregated error naming every URL it tried and why each one failed. Whenever the source's `download` is pinned with a `sha256`, the downloaded bytes are hashed and checked against it **no matter which path supplied them** G�� a mirror serving stale or tampered bytes is rejected and the next source in the chain is tried instead.
|
|
65
82
|
|
|
66
|
-
`--offline` skips the network entirely: it succeeds immediately if `<dir>/models/<source-id>/` already has files in it, and otherwise fails fast with a message telling you to pull once on a connected machine or point `--mirror`/`JGENGINE_ASSETS_MIRROR` at a reachable archive
|
|
83
|
+
`--offline` skips the network entirely: it succeeds immediately if `<dir>/models/<source-id>/` already has files in it, and otherwise fails fast with a message telling you to pull once on a connected machine or point `--mirror`/`JGENGINE_ASSETS_MIRROR` at a reachable archive G�� useful for CI scripts that should error in seconds instead of hanging on a blocked fetch.
|
|
67
84
|
|
|
68
|
-
**Network-restricted environments**
|
|
85
|
+
**Network-restricted environments** G�� a `403` on `CONNECT` from an outbound proxy means the environment's network policy blocks that provider host (common in sandboxed cloud sessions); it is a policy decision, not a transient failure, so retrying never helps. Either allowlist the host in the environment settings, or pull once on a machine that can reach the providers, then:
|
|
69
86
|
|
|
70
87
|
- commit (or otherwise host) the resulting `public/models/<source-id>/` directory so restricted environments read it straight off disk and use `assets pull --offline` as a fast, honest no-op check, or
|
|
71
88
|
- host your own mirror of the zip archives at `<baseUrl>/<provider>/<source-id>.zip` and set `JGENGINE_ASSETS_MIRROR=<baseUrl>` (or pass `--mirror <baseUrl>`) so `pull` fetches from it instead of the original provider.
|
|
72
89
|
|
|
73
90
|
## Adding assets
|
|
74
91
|
|
|
75
|
-
**A whole new pack**
|
|
92
|
+
**A whole new pack** G�� add one entry to the matching `src/sources/*.ts` (this is the layer contributors PR). No filenames are hand-typed; `reindex` reads the real `.glb` names out of the extracted pack, so entries can't silently 404.
|
|
76
93
|
|
|
77
94
|
```ts
|
|
78
|
-
// src/sources/
|
|
79
|
-
{ id: "
|
|
95
|
+
// src/sources/quaternius.ts G�� QUATERNIUS_PACKS
|
|
96
|
+
{ id: "quaternius-fantasy-props", slug: "fantasypropsmegakit", title: "Fantasy Props MegaKit", categories: ["fantasy", "prop"] },
|
|
80
97
|
```
|
|
81
98
|
|
|
82
99
|
```bash
|
|
83
|
-
bun src/cli/pull.ts pull
|
|
84
|
-
bun src/cli/pull.ts reindex ../../apps/dev/public/models
|
|
85
|
-
bun src/cli/pull.ts verify
|
|
100
|
+
bun src/cli/pull.ts pull quaternius-fantasy-props --dir ../../apps/dev/public # fetch + extract GLBs
|
|
101
|
+
bun src/cli/pull.ts reindex ../../apps/dev/public/models # regenerate generated/*.json + barrel
|
|
102
|
+
bun src/cli/pull.ts verify # license + alias gate
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
A **new provider** is just a new `src/sources/<provider>.ts` added to the `sources` array in `src/sources/index.ts`. Pinned providers use `download: { url, sha256? }`; providers
|
|
105
|
+
A **new provider** is just a new `src/sources/<provider>.ts` added to the `sources` array in `src/sources/index.ts`. Pinned providers use `download: { url, sha256? }`; providers whose pages JS-gate the download and can't be pinned use `download: { scrape: <page> }`. Any entry can also carry an optional top-level `mirror: <archiveUrl>` G�� a direct URL `pull` falls back to if both a `--mirror`/`JGENGINE_ASSETS_MIRROR` override and the primary path fail (see "Mirror fallback and offline pulls" above). Kenney.nl is not an eligible provider for this repo G�� see CLAUDE.md "Never Kenney".
|
|
106
|
+
|
|
107
|
+
**A whole new sprite/icon pack** G�� same shape as a model pack, just `kind: "sprite"` and `reindex-sprites` instead of `reindex`; files are matched by `.svg`/`.png` extension, deduped by basename regardless of archive nesting, so no filenames are hand-typed here either. `src/sources/gameicons.ts` is the current example (`gameicons-icons`); a new sprite provider follows the same shape in its own `src/sources/<provider>.ts`.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bun src/cli/pull.ts pull gameicons-icons --dir ../../apps/dev/public # fetch + extract SVG/PNG
|
|
111
|
+
bun src/cli/pull.ts reindex-sprites ../../apps/dev/public/sprites # regenerate generated-sprites/*.json + barrel
|
|
112
|
+
```
|
|
89
113
|
|
|
90
|
-
**A single one-off**
|
|
114
|
+
**A single one-off** G�� no code edit, zero bytes stored (URL) or copied into `local/` (path):
|
|
91
115
|
|
|
92
116
|
```bash
|
|
93
|
-
assets add "https://poly.pizza
|
|
117
|
+
assets add "https://poly.pizza/GǪ/model.glb" --category prop --license CC0-1.0 --author "Some Author"
|
|
94
118
|
```
|
|
95
119
|
|
|
96
120
|
## Importing in code
|
|
97
121
|
|
|
98
|
-
Build the catalog once, then address ids (or aliases)
|
|
122
|
+
Build the catalog once, then address ids (or aliases) G�� resolve returns `{ url }`:
|
|
99
123
|
|
|
100
124
|
```ts
|
|
101
125
|
import { buildCatalog } from "@jgengine/assets";
|
|
102
126
|
|
|
103
127
|
const catalog = buildCatalog({ basePath: "/models" });
|
|
104
128
|
|
|
105
|
-
catalog.resolve("
|
|
106
|
-
catalog.resolve("nature/tree_pine");
|
|
129
|
+
catalog.resolve("quaternius-stylized-nature/Pine_1"); // { url: "/models/quaternius-stylized-nature/Pine_1.glb" }
|
|
130
|
+
catalog.resolve("nature/tree_pine"); // alias G�� same url
|
|
107
131
|
|
|
108
132
|
// wire ids straight into a game's model seams:
|
|
109
133
|
export const game: PlayableGame = {
|
|
110
|
-
//
|
|
134
|
+
// GǪ
|
|
111
135
|
objectModels: {
|
|
112
|
-
"
|
|
136
|
+
"quaternius-stylized-nature/Pine_1": { url: catalog.resolve("quaternius-stylized-nature/Pine_1")!.url, scale: 1.4 },
|
|
113
137
|
},
|
|
114
138
|
entityModels: {
|
|
115
|
-
hero: { url: catalog.resolve("
|
|
139
|
+
hero: { url: catalog.resolve("kaykit-adventurers/Barbarian")!.url, scale: 1.1 },
|
|
116
140
|
},
|
|
117
141
|
};
|
|
118
142
|
```
|
|
119
143
|
|
|
120
|
-
`buildCatalog({ sources: ["
|
|
144
|
+
`buildCatalog({ sources: ["quaternius-stylized-nature"] })` restricts to chosen packs; `includeAliases` / `includeSingles` default true. Discover ids with `assets add <query>` (or `assets search <term>` / `assets list --category <c>`) instead of memorizing them.
|
|
145
|
+
|
|
146
|
+
Materials resolve the same way G�� ids (or `material/GǪ` aliases) to normalized PBR map URLs:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { buildMaterialCatalog } from "@jgengine/assets";
|
|
150
|
+
|
|
151
|
+
const materials = buildMaterialCatalog({ basePath: "/materials" });
|
|
152
|
+
|
|
153
|
+
materials.resolve("ambientcg-grass001")!.maps.color; // "/materials/ambientcg-grass001/color.jpg"
|
|
154
|
+
materials.resolve("material/grass")!.maps.normal; // alias G�� "/materials/ambientcg-grass001/normal.jpg"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Sprite/icon packs resolve the same way once pulled + reindexed G�� individual files, not whole packs:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { buildSpriteCatalog } from "@jgengine/assets";
|
|
161
|
+
|
|
162
|
+
const sprites = buildSpriteCatalog({ basePath: "/sprites" });
|
|
163
|
+
|
|
164
|
+
sprites.resolve("gameicons-icons/sword")!.url; // "/sprites/gameicons-icons/sword.svg"
|
|
165
|
+
```
|
|
121
166
|
|
|
122
167
|
### Serving the bytes
|
|
123
168
|
|
|
124
|
-
A resolved id only yields a **URL**
|
|
169
|
+
A resolved id only yields a **URL** G�� the GLB must be somewhere your app serves. For a game, `pull` the packs into your app's `public/`, and line `basePath` up with it:
|
|
125
170
|
|
|
126
171
|
```bash
|
|
127
|
-
bun src/cli/pull.ts pull
|
|
172
|
+
bun src/cli/pull.ts pull quaternius-stylized-nature --dir ../../apps/dev/public # G�� apps/dev/public/models/quaternius-stylized-nature/*.glb
|
|
128
173
|
```
|
|
129
174
|
|
|
130
|
-
`buildCatalog({ basePath: "/models" })` then resolves to `/models/
|
|
175
|
+
`buildCatalog({ basePath: "/models" })` then resolves to `/models/quaternius-stylized-nature/GǪ`, which the dev server serves from `public/models/` (gitignored G�� the bytes are the consumer's, fetched once from the provider's CDN).
|
|
176
|
+
|
|
177
|
+
Sprite/icon packs pull the same way, into `public/sprites/`:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
bun src/cli/pull.ts pull gameicons-icons --dir ../../apps/dev/public # G�� apps/dev/public/sprites/gameicons-icons/*.svg
|
|
181
|
+
bun src/cli/pull.ts reindex-sprites ../../apps/dev/public/sprites # G�� src/generated-sprites/gameicons-icons.json
|
|
182
|
+
```
|
|
131
183
|
|
|
132
184
|
## Notes
|
|
133
185
|
|
|
134
|
-
-
|
|
135
|
-
- Quaternius / KayKit pages gate downloads behind JS; automated `pull` falls back to a clear error when no archive link is found — download those manually into the staging dir, then `reindex`.
|
|
186
|
+
- Quaternius / KayKit pages gate downloads behind JS; sources without a pinned OpenGameArt/GitHub zip `scrape` the pack page as a fallback, and automated `pull` falls back to a clear error when no archive link is found G�� download those manually into the staging dir, then `reindex`.
|
|
136
187
|
- `pull`'s mirror fallback and `--offline` guard exist for network-restricted environments (CI, sandboxes without provider access); see "Mirror fallback and offline pulls" above.
|
|
188
|
+
- `gameicons-icons` is **CC BY 3.0**, not CC0 G�� a game using it needs one credit line (source, "game-icons.net contributors", license) per the engine's asset-credit rule; the repo's own per-author `license.txt` files carry the individual credits behind this pack's single collective `author` field.
|
|
189
|
+
- `src/generated-sprites/` starts empty for a brand-new sprite source G�� same as any brand-new model pack, the committed per-file index only exists after someone runs `pull` + `reindex-sprites` with real network access; until then the source is fully declared (and the weekly mirror job will fetch it) but has no individually-addressable ids yet.
|
package/dist/aliases.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { AssetAlias } from "./manifest.js";
|
|
2
|
+
/**
|
|
3
|
+
* Semantic keys → live catalog ids. Prefer these in games so re-homes only
|
|
4
|
+
* touch this table. Never point at kenney-*. Curated starter theme ids
|
|
5
|
+
* (`asset:person_casual`, `nature_tree`, …) come from {@link starterAliases}.
|
|
6
|
+
*/
|
|
2
7
|
export declare const aliases: readonly AssetAlias[];
|
package/dist/aliases.js
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
|
+
import { starterAliases } from "./packs/starter.js";
|
|
2
|
+
/**
|
|
3
|
+
* Semantic keys → live catalog ids. Prefer these in games so re-homes only
|
|
4
|
+
* touch this table. Never point at kenney-*. Curated starter theme ids
|
|
5
|
+
* (`asset:person_casual`, `nature_tree`, …) come from {@link starterAliases}.
|
|
6
|
+
*/
|
|
1
7
|
export const aliases = [
|
|
2
|
-
{ key: "nature/tree_pine", target: "
|
|
3
|
-
{ key: "nature/
|
|
4
|
-
{ key: "nature/
|
|
5
|
-
{ key: "nature/
|
|
6
|
-
{ key: "nature/
|
|
7
|
-
{ key: "
|
|
8
|
-
{ key: "
|
|
9
|
-
{ key: "
|
|
10
|
-
{ key: "
|
|
8
|
+
{ key: "nature/tree_pine", target: "quaternius-stylized-nature/Pine_1" },
|
|
9
|
+
{ key: "nature/tree_common", target: "quaternius-stylized-nature/CommonTree_1" },
|
|
10
|
+
{ key: "nature/tree_dead", target: "quaternius-stylized-nature/DeadTree_1" },
|
|
11
|
+
{ key: "nature/bush", target: "quaternius-stylized-nature/Bush_Common" },
|
|
12
|
+
{ key: "nature/rock", target: "quaternius-stylized-nature/Rock_Medium_1" },
|
|
13
|
+
{ key: "character/rogue", target: "kaykit-adventurers/Rogue" },
|
|
14
|
+
{ key: "character/knight", target: "kaykit-adventurers/Knight" },
|
|
15
|
+
{ key: "character/mage", target: "kaykit-adventurers/Mage" },
|
|
16
|
+
{ key: "character/barbarian", target: "kaykit-adventurers/Barbarian" },
|
|
17
|
+
{ key: "scifi/alien", target: "quaternius-modular-scifi/Alien_Cyclop" },
|
|
18
|
+
{ key: "scifi/chest", target: "quaternius-modular-scifi/Prop_Chest" },
|
|
19
|
+
{ key: "scifi/crate", target: "quaternius-modular-scifi/Prop_Crate3" },
|
|
20
|
+
{ key: "scifi/computer", target: "quaternius-modular-scifi/Prop_Computer" },
|
|
21
|
+
{ key: "dungeon/chest", target: "kaykit-dungeon/chest" },
|
|
22
|
+
{ key: "furniture/bed", target: "kaykit-furniture/bed_double_A" },
|
|
23
|
+
{ key: "furniture/couch", target: "kaykit-furniture/couch" },
|
|
24
|
+
{ key: "furniture/table", target: "kaykit-furniture/table_medium" },
|
|
25
|
+
{ key: "city/car_sedan", target: "kaykit-city-builder/car_sedan" },
|
|
26
|
+
{ key: "city/car_police", target: "kaykit-city-builder/car_police" },
|
|
27
|
+
{ key: "city/building_a", target: "kaykit-city-builder/building_A" },
|
|
28
|
+
{ key: "city/streetlight", target: "kaykit-city-builder/streetlight" },
|
|
29
|
+
...starterAliases(),
|
|
11
30
|
];
|
package/dist/catalogs/build.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createAssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
|
2
2
|
import { aliases } from "../aliases.js";
|
|
3
3
|
import { generatedIndex } from "../generated/index.js";
|
|
4
4
|
import { singles } from "../singles.js";
|
|
5
|
+
import { sources as declaredSources } from "../sources/index.js";
|
|
5
6
|
export function entryUrl(basePath, entry) {
|
|
6
7
|
return `${basePath.replace(/\/+$/, "")}/${entry.source}/${entry.file}`;
|
|
7
8
|
}
|
|
@@ -10,6 +11,14 @@ export function buildCatalog(options = {}) {
|
|
|
10
11
|
const includeAliases = options.includeAliases ?? true;
|
|
11
12
|
const includeSingles = options.includeSingles ?? true;
|
|
12
13
|
const sourceFilter = options.sources === undefined ? null : new Set(options.sources);
|
|
14
|
+
if (sourceFilter !== null) {
|
|
15
|
+
const knownSourceIds = new Set(declaredSources.map((source) => source.id));
|
|
16
|
+
for (const id of sourceFilter) {
|
|
17
|
+
if (!knownSourceIds.has(id)) {
|
|
18
|
+
throw new Error(`buildCatalog: unknown source id "${id}" — not declared in packages/assets/src/sources/*.ts`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
13
22
|
const catalog = createAssetCatalog();
|
|
14
23
|
for (const entry of generatedIndex) {
|
|
15
24
|
if (sourceFilter !== null && !sourceFilter.has(entry.source))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
2
|
+
/** Options for `buildSpriteCatalog`. */
|
|
3
|
+
export interface BuildSpriteCatalogOptions {
|
|
4
|
+
/** URL prefix where pulled sprite/icon files live (consumer's `public/sprites`). */
|
|
5
|
+
basePath?: string;
|
|
6
|
+
/** Restrict to these source ids; omit for all. */
|
|
7
|
+
sources?: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
/** Resolves individual pulled sprite/icon ids (e.g. `gameicons-icons/sword`) to `{ url }`. */
|
|
10
|
+
export declare function buildSpriteCatalog(options?: BuildSpriteCatalogOptions): AssetCatalog;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createAssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
2
|
+
import { generatedSpriteIndex } from "../generated-sprites/index.js";
|
|
3
|
+
import { entryUrl } from "./build.js";
|
|
4
|
+
/** Resolves individual pulled sprite/icon ids (e.g. `gameicons-icons/sword`) to `{ url }`. */
|
|
5
|
+
export function buildSpriteCatalog(options = {}) {
|
|
6
|
+
const basePath = options.basePath ?? "/sprites";
|
|
7
|
+
const sourceFilter = options.sources === undefined ? null : new Set(options.sources);
|
|
8
|
+
const catalog = createAssetCatalog();
|
|
9
|
+
for (const entry of generatedSpriteIndex) {
|
|
10
|
+
if (sourceFilter !== null && !sourceFilter.has(entry.source))
|
|
11
|
+
continue;
|
|
12
|
+
catalog.register(entry.id, { url: entryUrl(basePath, entry) });
|
|
13
|
+
}
|
|
14
|
+
return catalog;
|
|
15
|
+
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { type BuildCatalogOptions } from "./build.js";
|
|
2
|
+
/**
|
|
3
|
+
* Catalog restricted to the curated starter packs (people/props/nature/urban).
|
|
4
|
+
* Prefer this for scaffolds and probes so `asset:person_casual` etc. resolve
|
|
5
|
+
* without pulling the whole library. Full index: {@link buildCatalog}.
|
|
6
|
+
*/
|
|
2
7
|
export declare function createStarterCatalog(options?: BuildCatalogOptions): import("@jgengine/core/scene/assetCatalog").AssetCatalog<import("@jgengine/core/scene/assetCatalog").ModelAssetRef>;
|
package/dist/catalogs/starter.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import { STARTER_SOURCE_PACKS } from "../packs/starter.js";
|
|
1
2
|
import { buildCatalog } from "./build.js";
|
|
3
|
+
/**
|
|
4
|
+
* Catalog restricted to the curated starter packs (people/props/nature/urban).
|
|
5
|
+
* Prefer this for scaffolds and probes so `asset:person_casual` etc. resolve
|
|
6
|
+
* without pulling the whole library. Full index: {@link buildCatalog}.
|
|
7
|
+
*/
|
|
2
8
|
export function createStarterCatalog(options = {}) {
|
|
3
|
-
return buildCatalog(
|
|
9
|
+
return buildCatalog({
|
|
10
|
+
...options,
|
|
11
|
+
sources: options.sources ?? STARTER_SOURCE_PACKS,
|
|
12
|
+
});
|
|
4
13
|
}
|
package/dist/cli/paths.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
/** Sibling of `cli/` under `src/` (dev) or `dist/` (published) so reindex writes the tree consumers import.
|
|
1
|
+
/** Sibling of `cli/` under `src/` (dev) or `dist/` (published) so reindex writes the tree consumers import.
|
|
2
|
+
* @internal
|
|
3
|
+
*/
|
|
2
4
|
export declare function resolveGeneratedDir(cliDir: string): string;
|
|
5
|
+
/** Sprite-pack counterpart of `resolveGeneratedDir` — a separate generated tree, same sibling layout.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveGeneratedSpritesDir(cliDir: string): string;
|
|
9
|
+
/** @internal */
|
|
3
10
|
export declare function resolvePackageTreeRoot(cliDir: string): string;
|
|
11
|
+
/** @internal */
|
|
4
12
|
export declare function resolvePackageRoot(cliDir: string): string;
|
package/dist/cli/paths.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
|
-
/** Sibling of `cli/` under `src/` (dev) or `dist/` (published) so reindex writes the tree consumers import.
|
|
2
|
+
/** Sibling of `cli/` under `src/` (dev) or `dist/` (published) so reindex writes the tree consumers import.
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
3
5
|
export function resolveGeneratedDir(cliDir) {
|
|
4
6
|
return join(resolve(cliDir, ".."), "generated");
|
|
5
7
|
}
|
|
8
|
+
/** Sprite-pack counterpart of `resolveGeneratedDir` — a separate generated tree, same sibling layout.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export function resolveGeneratedSpritesDir(cliDir) {
|
|
12
|
+
return join(resolve(cliDir, ".."), "generated-sprites");
|
|
13
|
+
}
|
|
14
|
+
/** @internal */
|
|
6
15
|
export function resolvePackageTreeRoot(cliDir) {
|
|
7
16
|
return resolve(cliDir, "..");
|
|
8
17
|
}
|
|
18
|
+
/** @internal */
|
|
9
19
|
export function resolvePackageRoot(cliDir) {
|
|
10
20
|
return resolve(cliDir, "..", "..");
|
|
11
21
|
}
|
package/dist/cli/pull.d.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** Sibling of `cli/` under `src/` (dev) or `dist/` (published) so reindex writes the tree consumers import. */
|
|
3
3
|
export declare const generatedDir: string;
|
|
4
|
+
/** Sprite-pack counterpart of `generatedDir`. */
|
|
5
|
+
export declare const generatedSpritesDir: string;
|
|
4
6
|
export { resolveGeneratedDir } from "./paths.js";
|
|
7
|
+
/** @internal */
|
|
5
8
|
export declare function flag(argv: string[], name: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* The runtime's own fetch, with a curl fallback for hosts it cannot reach —
|
|
11
|
+
* some proxied sandboxes tear down bun's TLS stream to GitHub's release-asset
|
|
12
|
+
* host while curl (which honors SSL_CERT_FILE) gets through fine.
|
|
13
|
+
*/
|
|
14
|
+
export declare const cliFetch: typeof fetch;
|
|
15
|
+
/** @internal */
|
|
6
16
|
export declare function describeNetworkFailure(error: unknown): string;
|
|
17
|
+
/** @internal */
|
|
7
18
|
export declare function isPopulated(dir: string): boolean;
|
|
19
|
+
/** @internal */
|
|
8
20
|
export declare function cmdPull(argv: string[]): Promise<void>;
|