create-playcanvas 0.8.1 → 0.10.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.
Files changed (51) hide show
  1. package/package.json +2 -2
  2. package/skills/add-effects/SKILL.md +21 -83
  3. package/skills/apply-conventions/SKILL.md +33 -59
  4. package/skills/assemble-scene/SKILL.md +18 -43
  5. package/skills/assemble-scene/references/direct-engine.md +2 -2
  6. package/skills/assemble-scene/references/react.md +3 -3
  7. package/skills/assemble-scene/references/web-components.md +2 -2
  8. package/skills/bake-lighting/SKILL.md +25 -56
  9. package/skills/build-app/SKILL.md +38 -37
  10. package/skills/build-app/references/web-components.md +3 -3
  11. package/skills/calibrate-model/SKILL.md +18 -48
  12. package/skills/calibrate-model/references/web-components.md +3 -3
  13. package/skills/configure-animation/SKILL.md +21 -37
  14. package/skills/configure-animation/references/direct-engine.md +2 -2
  15. package/skills/configure-animation/references/react.md +3 -3
  16. package/skills/find-examples/SKILL.md +24 -60
  17. package/skills/inspect-glb/SKILL.md +21 -62
  18. package/skills/inspect-runtime/SKILL.md +41 -0
  19. package/skills/inspect-runtime/scripts/devtools-hook.js +23 -0
  20. package/skills/light-scene/SKILL.md +29 -97
  21. package/skills/load-assets/SKILL.md +52 -0
  22. package/skills/override-shader-chunks/SKILL.md +21 -61
  23. package/skills/reduce-draw-calls/SKILL.md +24 -54
  24. package/skills/reuse-scripts/SKILL.md +23 -46
  25. package/skills/reuse-scripts/references/direct-engine.md +3 -3
  26. package/skills/verify-pixels/SKILL.md +24 -58
  27. package/templates/engine/base/README.md +4 -0
  28. package/templates/engine/base/package.json +3 -2
  29. package/templates/engine/base/src/inspector.css +33 -0
  30. package/templates/engine/base/src/inspector.ts +38 -0
  31. package/templates/engine/base/tsconfig.json +1 -1
  32. package/templates/engine/base/vite.config.ts +16 -0
  33. package/templates/engine/interactive-sphere/src/App.ts +2 -2
  34. package/templates/engine/model-viewer/src/main.ts +1 -2
  35. package/templates/engine/product-configurator/src/main.ts +6 -7
  36. package/templates/react/base/README.md +4 -0
  37. package/templates/react/base/package.json +3 -2
  38. package/templates/react/base/src/inspector.css +33 -0
  39. package/templates/react/base/src/inspector.ts +38 -0
  40. package/templates/react/base/vite.config.ts +12 -1
  41. package/templates/react/product-configurator/src/App.tsx +6 -6
  42. package/templates/web-components/base/README.md +4 -0
  43. package/templates/web-components/base/package.json +4 -3
  44. package/templates/web-components/base/src/inspector.css +33 -0
  45. package/templates/web-components/base/src/inspector.ts +38 -0
  46. package/templates/web-components/base/tsconfig.json +1 -1
  47. package/templates/web-components/base/vite.config.ts +16 -0
  48. package/templates/web-components/interactive-sphere/src/main.ts +1 -2
  49. package/templates/web-components/product-configurator/src/main.ts +5 -6
  50. package/skills/build-hud/SKILL.md +0 -38
  51. package/skills/manage-game-state/SKILL.md +0 -48
@@ -1,65 +1,35 @@
1
1
  ---
2
2
  name: calibrate-model
3
- description: Use before repeatedly placing GLB models in PlayCanvas Engine, React, or Web Components to measure each asset once and record its uniform scale, grounding offset, and yaw correction in a stable nested transform.
3
+ description: Use before repeatedly placing GLB models in PlayCanvas to record each asset's uniform scale, grounding offset, pivot compensation, and yaw once.
4
4
  ---
5
5
 
6
6
  # Model calibration
7
7
 
8
- Use the `inspect-glb` skill to measure every unique GLB before mass placement. Rely on `dims` and
9
- `groundOffset` for contact-accurate placement only when it reports `boundsSource: vertices`. Store
10
- one tuning record per asset:
8
+ Measure each unique GLB once with `inspect-glb`, only trusting bounds when `boundsSource` is
9
+ `vertices`, and keep one tuning record per asset:
11
10
 
12
11
  ```ts
13
12
  const ASSET_TUNING = {
14
13
  model: {
15
- boundsSource: 'vertices',
16
- aabb: { min: [-4, -1, -9], max: [4, 6, 9] },
17
- dims: [8, 7, 18],
18
- center: [0, 2.5, 0],
19
- groundOffset: 1,
20
- intended: { dimension: 'length', size: 18 },
21
- scale: 0.9,
22
- y: 0.9,
23
- yaw: 180
14
+ boundsSource: 'vertices', dims: [8, 7, 18], center: [0, 2.5, 0], groundOffset: 1,
15
+ intended: { dimension: 'length', size: 18 }, scale: 1, y: 1, yaw: 180
24
16
  }
25
17
  } as const;
26
18
  ```
27
19
 
28
- ## Calculate the record
20
+ 1. Pick the dimension that defines the model's real size, in metres or relative to an already
21
+ calibrated model.
22
+ 2. Set `scale = intendedSize / measuredDimension` and, for floor-resting models,
23
+ `y = groundOffset * scale`. Record a deliberate offset for models that should not rest on the
24
+ floor.
25
+ 3. Confirm facing once in the running app and record `yaw`.
29
26
 
30
- 1. Pick and record the intended dimension and size: character height, building footprint, or
31
- vehicle length. Base it on world units or an already calibrated reference model.
32
- 2. Calculate `scale = intendedSize / measuredDimension`.
33
- 3. For a floor-resting model, calculate `y = groundOffset * scale`. Record a deliberate offset for
34
- waterlines, embedded objects, or airborne models.
35
- 4. Confirm directional facing once in the running app, as the `apply-conventions` skill describes.
36
- PlayCanvas entities face -Z while glTF convention is +Z, but asset packs vary.
37
- 5. Retain `boundsSource`, `aabb`, `dims`, `center`, `groundOffset`, and the intended size with
38
- `{ scale, y, yaw }`. Use the scaled footprint and centre for initial spacing; do not re-derive or
39
- add per-instance nudges.
27
+ Nest three levels so a root position means the same for every asset: the semantic root, a yaw
28
+ wrapper, then the visual child with the scale and local position
29
+ `(-center.x * scale, y, -center.z * scale)`. Apply calibration once, never per instance.
40
30
 
41
- Keep gameplay position and heading on an outer semantic root, and seat the model on one predictable
42
- reference point beneath it so a root position means the same thing for every asset: by default the
43
- footprint centre over the base. Apply the authored yaw on a wrapper, then the scale and the full
44
- offset on the render child inside it, so an off-centre pivot is compensated in the authored frame and
45
- never re-rotated by the yaw or by gameplay heading:
31
+ Place the root on a measured support point, such as a mount point or raycast, not a global AABB
32
+ maximum. Skinned bounds are bind-pose estimates; confirm contact in animated poses.
46
33
 
47
- ```ts
48
- const yaw = new Entity('yaw');
49
- yaw.setLocalEulerAngles(0, t.yaw, 0);
50
- const visual = instantiate(asset);
51
- visual.setLocalScale(t.scale, t.scale, t.scale);
52
- visual.setLocalPosition(-t.center[0] * t.scale, t.y, -t.center[2] * t.scale);
53
- yaw.addChild(visual);
54
- root.addChild(yaw);
55
- ```
56
-
57
- Keep `y` as the local correction that brings the measured minimum to the root plane; the X and Z
58
- terms bring the footprint centre onto the root axis. Place that root at a measured support point. A support name or global AABB maximum is not
59
- a surface measurement. Use an authored mount point or runtime support query for curved or stepped
60
- geometry. Treat skinned bounds as bind-pose estimates and confirm foot contact in the active poses.
61
-
62
- Read exactly one reference matching the code being edited:
63
- [direct Engine](references/direct-engine.md), [React](references/react.md), or
64
- [Web Components](references/web-components.md). Choose from imports and markup, not installed
65
- dependencies alone.
34
+ Read only the reference matching the code: [direct Engine](references/direct-engine.md),
35
+ [React](references/react.md), or [Web Components](references/web-components.md).
@@ -4,12 +4,12 @@ Keep gameplay placement on the outer entity and calibration on a visual child:
4
4
 
5
5
  ```html
6
6
  <pc-app>
7
- <pc-asset id="ship" src="/models/ship.glb"></pc-asset>
7
+ <pc-asset id="model" src="/models/model.glb"></pc-asset>
8
8
  <pc-scene>
9
- <pc-entity name="Ship" position="12 0 -4" rotation="0 90 0">
9
+ <pc-entity name="Model" position="12 0 -4" rotation="0 90 0">
10
10
  <pc-entity rotation="0 180 0">
11
11
  <pc-entity position="-0.45 1.2 0.225" scale="0.9 0.9 0.9">
12
- <pc-model asset="ship"></pc-model>
12
+ <pc-model asset="model"></pc-model>
13
13
  </pc-entity>
14
14
  </pc-entity>
15
15
  </pc-entity>
@@ -1,50 +1,34 @@
1
1
  ---
2
2
  name: configure-animation
3
- description: Use before playing, blending, or retargeting GLB animation clips in PlayCanvas Engine, React, or Web Components to inspect clip and joint names, attach animation to the rendered hierarchy, keep blend inputs valid, and diagnose T-pose, frozen playback, or binding failures.
3
+ description: Use before playing, blending, or retargeting GLB animation clips in PlayCanvas, or when diagnosing T-pose, frozen playback, or binding failures.
4
4
  ---
5
5
 
6
6
  # Animation setup
7
7
 
8
- Use the `inspect-glb` skill first. Never guess clip or joint names.
8
+ Get clip and joint names from `inspect-glb`; never guess them.
9
9
 
10
- ## Choose the smallest playback model
11
-
12
- - Use the selected authoring surface's simple playback path for a single clip.
13
- - Use an `AnimStateGraph` for locomotion blending, transitions, or event-driven actions.
14
- - Adapt the official animation examples with the `find-examples` skill instead of writing a state
15
- graph from memory.
16
-
17
- ## Keep blend inputs valid
18
-
19
- Clamp every 1D blend parameter to the first and last child points before updating it. PlayCanvas
20
- does not clamp values outside that span: every child can receive zero weight and synchronized clips
21
- can receive non-finite speeds, freezing the pose. Exercise the first, interior, and last blend
22
- points with real gameplay input.
23
-
24
- ## Retarget only compatible rigs
25
-
26
- For each source `animationTargets` entry, remove its final `.translation`, `.rotation`, `.scale`, or
27
- `.weights` suffix and match the remaining path against the destination's `nodePaths`. The top-level
28
- model root may differ; the remaining parent chain must match. Equal joint counts or similar bone
29
- names are insufficient. Confirm the result in the running app as the `apply-conventions` skill
30
- describes; frozen, partial, or exploded motion means the rigs are incompatible.
10
+ - For one clip, use the surface's simple playback path. For blending, transitions, or events, adapt
11
+ an `AnimStateGraph` from an official animation example with `find-examples`.
12
+ - Put the `anim` component on or above the rendered skinned hierarchy, not on a separate pivot.
13
+ - Clamp every 1D blend parameter to its first and last child points. The Engine does not; values
14
+ outside the span zero every weight and can make synced speeds non-finite, freezing the pose.
15
+ - Retarget only when each source `animationTargets` path, minus its `.translation`, `.rotation`,
16
+ `.scale`, or `.weights` suffix, matches a destination `nodePaths` entry below the model root.
17
+ Equal joint counts or similar names are not enough.
18
+ - Scale a wrapper, never skinned bones.
31
19
 
32
20
  ## Prove playback
33
21
 
34
- An assigned clip, active state, or advancing state time does not prove that the skeleton is moving.
35
- For clips expected to move, sample the local position or rotation of two non-root joints over
36
- several rendered frames. Require a finite animation speed, active-state time, and progress, and
37
- confirm pose changes at idle and at both ends of each blend tree. Zero joint motion indicates a
38
- binding, playback, or blend-weight failure even when reported state names look correct.
22
+ An active state or advancing state time does not prove motion. Sample two non-root joints over
23
+ several rendered frames (`inspect-runtime`) at idle and at both ends of each blend tree. Zero joint motion means a
24
+ binding, playback, or blend-weight failure.
39
25
 
40
- ## Diagnose failures
26
+ ## Diagnose
41
27
 
42
- - A T-pose usually means no clip was assigned, target paths differ, or animation is attached outside
43
- the rendered hierarchy.
44
- - Put scale on a model wrapper, not skinned bones.
45
- - Match playback rate to real movement speed when visible foot sliding matters.
28
+ - T-pose: no clip assigned, target paths differ, or the anim component sits outside the rendered
29
+ hierarchy.
30
+ - Frozen pose: an unclamped blend input or a non-finite speed.
31
+ - Partial or exploded motion: incompatible rigs.
46
32
 
47
- Read exactly one reference matching the code being edited:
48
- [direct Engine](references/direct-engine.md), [React](references/react.md), or
49
- [Web Components](references/web-components.md). Choose from imports and markup, not installed
50
- dependencies alone.
33
+ Read only the reference matching the code: [direct Engine](references/direct-engine.md),
34
+ [React](references/react.md), or [Web Components](references/web-components.md).
@@ -21,10 +21,10 @@ Place the anim component on or above the complete skinned render hierarchy befor
21
21
  Attaching it to an unrelated pivot does not bind the clip.
22
22
 
23
23
  Clamp 1D state-graph parameters to their authored child range. For children at `0.5`, `3.5`, and
24
- `8`, do not pass an unclamped movement speed:
24
+ `8`, do not pass an unclamped value:
25
25
 
26
26
  ```ts
27
- entity.anim?.setFloat('locomotion', Math.max(0.5, Math.min(8, speed)));
27
+ entity.anim?.setFloat('blend', Math.max(0.5, Math.min(8, value)));
28
28
  ```
29
29
 
30
30
  After assignment, drive the real gameplay inputs through the range and sample expected moving
@@ -7,13 +7,13 @@ import { Entity } from '@playcanvas/react';
7
7
  import { Anim, Render } from '@playcanvas/react/components';
8
8
  import { useModel } from '@playcanvas/react/hooks';
9
9
 
10
- const Character = () => {
11
- const { asset, error } = useModel('/models/Character.glb');
10
+ const Model = () => {
11
+ const { asset, error } = useModel('/models/model.glb');
12
12
  if (error) throw new Error(error);
13
13
  if (!asset) return null;
14
14
 
15
15
  return (
16
- <Entity name="Character">
16
+ <Entity name="Model">
17
17
  <Render type="asset" asset={asset} />
18
18
  <Anim asset={asset} activate />
19
19
  </Entity>
@@ -1,79 +1,43 @@
1
1
  ---
2
2
  name: find-examples
3
- description: Use when implementing an unfamiliar PlayCanvas feature such as animation, particles, shaders, physics, UI, splats, or XR to find the official Engine, React, or Web Components example matching the installed package version and adapt it to the project.
3
+ description: Use when implementing a PlayCanvas feature the project does not use yet, to find and adapt the official example matching the installed version.
4
4
  ---
5
5
 
6
6
  # Official examples
7
7
 
8
- Use official examples as version-matched recipes. Engine and Web Components examples are standalone
9
- applications to adapt, not modules to import.
8
+ Examples are version-matched recipes to adapt, not modules to import. When the Engine ships a script
9
+ for the behaviour, use `reuse-scripts` first.
10
10
 
11
- Use the `reuse-scripts` skill instead when the installed Engine ships a production script for the
12
- required behavior.
11
+ ## Find the example
13
12
 
14
- ## Find the matching example
15
-
16
- When `node_modules/playcanvas` is a local source checkout or linked build — a symlink, a workspace,
17
- or a path dependency — its `examples/src/examples/**` are already on disk. Read them directly and
18
- skip the GitHub fetch below. A linked engine can sit ahead of any published tag, and a pre-release
19
- version (`-alpha`, `-beta`) may have no matching public tree, so the on-disk source is the ground
20
- truth. Fall back to GitHub only when the examples are absent locally; the published npm package
21
- omits `examples/`.
22
-
23
- ```sh
24
- ls node_modules/playcanvas/examples/src/examples 2>/dev/null && echo "linked engine: read local examples, skip the fetch"
25
- ```
26
-
27
- Search the active surface's own catalog first, then the Engine catalog. Engine examples are the
28
- largest set and port to every surface; the per-surface catalogs are smaller but need no translation.
29
-
30
- | Surface | Repository | Package | Ref | Example paths |
31
- | --- | --- | --- | --- | --- |
32
- | direct Engine | `playcanvas/engine` | `playcanvas` | `v<version>` | `examples/src/examples/<category>/<name>.example.mjs` |
33
- | Web Components | `playcanvas/web-components` | `@playcanvas/web-components` | `v<version>` | `examples/<name>.html` |
34
- | React | `playcanvas/developer-site` | `@playcanvas/react` | `main` | `docs/user-manual/react/examples/<name>.mdx` |
35
-
36
- Resolve the installed version instead of assuming the newest API:
37
-
38
- ```sh
39
- PKG=playcanvas # or @playcanvas/web-components
40
- TAG="v$(node -p "JSON.parse(require('fs').readFileSync('node_modules/$PKG/package.json')).version")" || exit 1
41
- gh api "repos/playcanvas/engine/git/trees/$TAG?recursive=1" \
42
- | grep -o '"examples/src/examples/[^"]*\.example\.mjs"'
43
- ```
44
-
45
- Read the resolved package rather than the dependency list; `playcanvas` is often a transitive
46
- dependency of `@playcanvas/react` or `@playcanvas/web-components`. Stop on a failed read instead of
47
- requesting an invalid tag: an unresolved version produces an empty example list, not an error. When
48
- the package is not below the project root's `node_modules`, run the command from the workspace that
49
- depends on it.
50
-
51
- List a different surface by substituting its repository and path pattern, such as
52
- `'"examples/[^"]*\.html"'` for Web Components. Fetch a candidate from the same ref:
13
+ If `node_modules/playcanvas/examples/src/examples` exists, it is a linked or source checkout: read
14
+ it locally. The npm package omits `examples/`, so otherwise fetch from the installed tag:
53
15
 
54
16
  ```sh
17
+ TAG="v$(node -p "JSON.parse(require('fs').readFileSync('node_modules/playcanvas/package.json')).version")" || exit 1
18
+ gh api "repos/playcanvas/engine/git/trees/$TAG?recursive=1" | grep -o '"examples/src/examples/[^"]*\.example\.mjs"'
55
19
  gh api "repos/playcanvas/engine/contents/examples/src/examples/<category>/<name>.example.mjs?ref=$TAG" \
56
20
  --jq .content | base64 --decode
57
21
  ```
58
22
 
59
- Use `main` only when the installed version has no tag. It may contain unsupported APIs. Example
60
- asset URLs refer to `examples/assets/`; fetch assets from the same ref or substitute project assets.
23
+ Read the resolved package, not the dependency list; `playcanvas` is often transitive. An empty list
24
+ means an invalid tag: stop rather than guess. Use `main` only when the version has no tag.
61
25
 
62
- The React catalog is documentation rather than a tagged release: it is small, tracks the latest
63
- package, and its snippets are page fragments. Verify every prop and hook it uses against the
64
- installed `@playcanvas/react` declarations, and port an Engine example when no React example covers
65
- the feature.
26
+ | Surface | Repository | Ref | Example paths |
27
+ | --- | --- | --- | --- |
28
+ | direct Engine | `playcanvas/engine` | `v<version>` | `examples/src/examples/<category>/<name>.example.mjs` |
29
+ | Web Components | `playcanvas/web-components` | `v<version>` | `examples/<name>.html` |
30
+ | React | `playcanvas/developer-site` | `main` | `docs/user-manual/react/examples/<name>.mdx` |
66
31
 
67
- ## Adapt the recipe
32
+ Search the surface's catalog first, then the Engine's. React pages track the latest package; verify
33
+ each prop against the installed declarations. Fetch example assets, including the Draco, Basis, and
34
+ Ammo decoders under `examples/assets/wasm`, from the same ref.
68
35
 
69
- Keep component options, scene construction, materials, shaders, assets, and update logic. Drop the
70
- example browser's controls and context imports.
36
+ ## Adapt it
71
37
 
72
- When one example integrates several required systems, reproduce that integration as one baseline
73
- before splitting or customizing it. Copying one script's parameters while omitting its layers,
74
- depth map, camera frame, sky, or assets is not an adaptation of the example.
38
+ Keep component options, scene setup, materials, shaders, assets, and update logic; drop the example
39
+ browser's controls and imports. When one example joins several systems, such as layers, depth,
40
+ camera frame, and sky, get that baseline rendering before customizing it.
75
41
 
76
- Read exactly one reference matching the code being edited:
77
- [direct Engine](references/direct-engine.md), [React](references/react.md), or
78
- [Web Components](references/web-components.md). Choose from imports and markup, not installed
79
- dependencies alone.
42
+ Read only the reference matching the code: [direct Engine](references/direct-engine.md),
43
+ [React](references/react.md), or [Web Components](references/web-components.md).
@@ -1,81 +1,40 @@
1
1
  ---
2
2
  name: inspect-glb
3
- description: Use before loading, placing, scaling, or animating GLB assets in PlayCanvas to measure bounds, grounding offset, clips, joints, and hierarchy offline with the bundled zero-dependency inspector.
3
+ description: Use before loading, placing, scaling, or animating GLB models in PlayCanvas to measure bounds, grounding, clips, joints, and hierarchy with the bundled offline inspector.
4
4
  ---
5
5
 
6
6
  # Inspect GLBs
7
7
 
8
- Resolve `scripts/inspect.mjs` relative to this skill directory and run it before choosing entity
9
- transforms or animation names:
8
+ Resolve `scripts/inspect.mjs` relative to this skill and run it before choosing transforms or
9
+ animation names. Shortlist files; do not dump a whole asset pack into context.
10
10
 
11
11
  ```sh
12
- node <skill-directory>/scripts/inspect.mjs public/models/Character.glb
13
- node <skill-directory>/scripts/inspect.mjs public/models/{Ship,Enemy}_*.glb
12
+ node <skill-directory>/scripts/inspect.mjs public/models/*.glb
14
13
  ```
15
14
 
16
- The output provides:
15
+ ## Read the output
17
16
 
18
- - `dims` for calculating a uniform scale;
19
- - `groundOffset` for the model wrapper's local Y position;
20
- - `center` for the pivot offset from the geometry centre: a non-zero X or Z means the root's
21
- position is not where the model appears, and Y feeds grounding;
22
- - `boundsSource` and `boundsPose` for proving how and at which pose the bounds were measured;
23
- - `requiresRuntimeCheck` for animated morphs, skins, and incomplete decoding;
24
- - `nodePaths`, `clips`, `joints`, and `animationTargets` for animation setup and rig comparison;
25
- - `morphed`, `morphAnimated`, and `skinned` for pose-sensitive assets.
17
+ - `dims`, `center`, and `groundOffset` give the uniform scale input, the pivot offset, and the Y
18
+ seat. Non-zero `center.x` or `center.z` means the model does not sit on its root.
19
+ - `boundsSource`: trust bounds for placement only when it is `vertices`. `accessor-minmax` can
20
+ over-estimate rotated nodes by up to 41 percent; `incomplete` or `null` means do not calibrate.
21
+ `boundsNotes` gives the cause.
22
+ - `boundsPose`: `static` and `default-morph` are the authored pose; `bind` is the skinned rest pose.
23
+ - `requiresRuntimeCheck` flags morphs, skins, or partial decoding; confirm extents in the app.
24
+ - `nodePaths`, `clips`, `joints`, and `animationTargets` feed `configure-animation`.
26
25
 
27
- ## Bounds precision
26
+ Bounds cannot prove facing. Confirm it in the running app and record the yaw with
27
+ `calibrate-model`. Runtime `meshInstance.aabb` expands conservatively for morphs, so place static
28
+ models from inspected bounds.
28
29
 
29
- `aabb`, `dims`, `center`, and `groundOffset` are decoded from actual vertex positions, default morph
30
- weights, and each node's world matrix. Confirm both source and pose before relying on them:
30
+ ## Compressed files
31
31
 
32
- - `vertices` means every primitive was decoded and the bounds are exact for `boundsPose`.
33
- - `accessor-minmax` means at least one primitive could not be decoded, so its bounds fall back to the
34
- node-transformed local box. That over-estimates a mesh which does not fill its own box on a node
35
- rotated off-axis, by up to 41 percent for a 45-degree yaw. `boundsNotes` gives the cause.
36
- - `incomplete` means some visible default-pose geometry could not be measured; do not calibrate from
37
- the reported bounds.
38
- - `null` means no usable bounds could be recovered.
39
-
40
- `static` and `default-morph` bounds describe visible geometry at its authored default pose. `bind`
41
- describes skinned vertices before animation. When `requiresRuntimeCheck` is true, confirm grounding
42
- and maximum animated extent in the running application even if `boundsSource` is `vertices`.
43
-
44
- Carry `boundsSource`, `aabb`, `dims`, `center`, and `groundOffset` into the asset tuning record.
45
- `groundOffset` seats the base in Y; `center.x` and `center.z` are the horizontal pivot offset that
46
- `calibrate-model` must compensate, so a placed root sits where the mesh appears, not where the
47
- authored pivot happens to be. Running the inspector alone is not placement evidence. Its global AABB supports plane grounding and
48
- broad-phase spacing, but cannot locate a non-planar support surface or attachment point. Use an
49
- authored mount point or confirm the support in the running application before placing another model.
50
-
51
- ## Extract from compressed containers
52
-
53
- The bundled inspector decodes plain, interleaved, quantized, and sparse vertex buffers. For Draco,
54
- Meshopt, or external buffers, `boundsNotes` names the cause. Parse and rewrite a scratch copy with
55
- glTF Transform's lossless `copy` command, then inspect that copy rather than shipping it:
32
+ The inspector decodes plain, interleaved, quantized, and sparse buffers. For Draco, Meshopt, or
33
+ external buffers, inspect a lossless scratch copy; do not use `optimize`, which applies extra
34
+ transforms:
56
35
 
57
36
  ```sh
58
37
  npx --yes @gltf-transform/cli@4.4.2 copy in.glb scratch.glb
59
- node <skill-directory>/scripts/inspect.mjs scratch.glb
60
38
  ```
61
39
 
62
- `copy` preserves decoded vertex values, scenes, nodes, and materials while removing Draco and
63
- Meshopt compression. Existing lossy compression may already have quantized the source. Do not
64
- substitute `optimize`: it enables additional transforms unless each one is understood and disabled.
65
-
66
- The Engine has no `EXT_meshopt_compression` support, so a meshopt asset will not render as shipped.
67
- If it must appear in the scene, transcode it to an uncompressed GLB the same way — subject to the
68
- project's rules on modifying assets — so it both loads and measures.
69
-
70
- These geometry-fit bounds can intentionally disagree with the Engine's runtime culling AABB.
71
- `MeshInstance.aabb` transforms the declared local box and expands it conservatively for morph
72
- targets, so it can be larger than visible geometry. Place static models from exact inspected bounds;
73
- confirm morph and skin extremes at runtime. A skinned mesh node's own transform is ignored here
74
- because glTF requires it; the Engine resolves to the same result.
75
-
76
- Shortlist files first; do not dump an entire asset pack into context. Bounds cannot prove facing.
77
- glTF convention is +Z forward while PlayCanvas entities face -Z, and asset packs vary. Confirm each
78
- directional model once in the running app as the `apply-conventions` skill describes, and store its
79
- yaw correction with its scale and grounding.
80
-
81
- Use `npx @gltf-transform/cli inspect` only when vertex or texture statistics justify extra tooling.
40
+ The Engine cannot load `EXT_meshopt_compression`; see `load-assets` before shipping such files.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: inspect-runtime
3
+ description: Use when reading state from a running PlayCanvas app, to reach its live Application through the Engine devtools hook without changing app code.
4
+ ---
5
+
6
+ # Inspect a running app
7
+
8
+ From Engine 2.23, every app announces itself to a hook at
9
+ `globalThis[Symbol.for('playcanvas.inspector')]`. Read live state through it instead of adding
10
+ `window.app` or debug code to the app.
11
+
12
+ ## Attach
13
+
14
+ - Resolve `scripts/devtools-hook.js` relative to this skill and inject it as an init script that
15
+ runs before any page script. A hook defined later misses existing apps; reload instead.
16
+ - Page code then reads `globalThis.__playcanvasApps`, a `Map` from each live app to
17
+ `{ version, revision, protocol }`. `app.init()` adds the entry and `app.destroy()` removes it.
18
+ - The script chains a hook defined before it, such as a devtools extension's.
19
+ - No entry means the Engine predates 2.23, the hook loaded late, or the app set
20
+ `devtools: false`. The Engine swallows errors thrown by the hook.
21
+ - In 2.23.0 only the debug build (`playcanvas/debug`, `build/playcanvas.dbg.*`) announces apps; the
22
+ default import never calls the hook. Later 2.23 patches fix every build.
23
+
24
+ ## Read state
25
+
26
+ - Read after `frameend`, so values reflect a rendered frame.
27
+ - Return plain numbers and strings to the caller, not Engine objects.
28
+ - Useful reads: `app.root.findByName()` and `findByTag()`, world positions and
29
+ `meshInstance.aabb`, `app.stats.drawCallCount` and `frameTime`, and
30
+ `entity.anim.baseLayer.activeState`.
31
+ - Pause with `app.timeScale = 0`; `update` still runs with a zero `dt`. Step one frame by restoring
32
+ the scale, then zeroing it again on the next `frameend`.
33
+ - Revert anything you change to take a measurement.
34
+
35
+ ## Inspector panel
36
+
37
+ `@playcanvas/inspector` adds a panel for people: `new Inspector(app)` shows the hierarchy, assets,
38
+ materials, shaders, textures, render targets, frame graph, and physics. It must import the app's
39
+ own `playcanvas` package, so add it only to development builds. It draws over the canvas and
40
+ applies view overrides, so destroy it before `verify-pixels` captures. Stepping through draws
41
+ needs the debug build.
@@ -0,0 +1,23 @@
1
+ // Records every PlayCanvas app (Engine 2.23+) in globalThis.__playcanvasApps, a Map from app to
2
+ // { version, revision, protocol }. Run it before any page script; apps created earlier are missed.
3
+ // Plain script with no imports or exports, so it runs as an init script, a <script>, or an import.
4
+ {
5
+ const key = Symbol.for('playcanvas.inspector');
6
+ const prev = globalThis[key];
7
+ const apps = (globalThis.__playcanvasApps ??= new Map());
8
+
9
+ // chain a hook defined earlier, such as a devtools extension's, and install only once
10
+ if (!prev?.playcanvasApps) {
11
+ globalThis[key] = {
12
+ playcanvasApps: apps,
13
+ register(app, info) {
14
+ apps.set(app, info);
15
+ prev?.register?.(app, info);
16
+ },
17
+ unregister(app) {
18
+ apps.delete(app);
19
+ prev?.unregister?.(app);
20
+ }
21
+ };
22
+ }
23
+ }