create-playcanvas 0.8.1 → 0.9.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/package.json +1 -1
- package/skills/add-effects/SKILL.md +21 -83
- package/skills/apply-conventions/SKILL.md +33 -59
- package/skills/assemble-scene/SKILL.md +18 -43
- package/skills/assemble-scene/references/direct-engine.md +2 -2
- package/skills/assemble-scene/references/react.md +3 -3
- package/skills/assemble-scene/references/web-components.md +2 -2
- package/skills/bake-lighting/SKILL.md +25 -56
- package/skills/build-app/SKILL.md +36 -37
- package/skills/build-app/references/web-components.md +3 -3
- package/skills/calibrate-model/SKILL.md +18 -48
- package/skills/calibrate-model/references/web-components.md +3 -3
- package/skills/configure-animation/SKILL.md +21 -37
- package/skills/configure-animation/references/direct-engine.md +2 -2
- package/skills/configure-animation/references/react.md +3 -3
- package/skills/find-examples/SKILL.md +24 -60
- package/skills/inspect-glb/SKILL.md +21 -62
- package/skills/light-scene/SKILL.md +29 -97
- package/skills/load-assets/SKILL.md +52 -0
- package/skills/override-shader-chunks/SKILL.md +21 -61
- package/skills/reduce-draw-calls/SKILL.md +24 -54
- package/skills/reuse-scripts/SKILL.md +23 -46
- package/skills/reuse-scripts/references/direct-engine.md +3 -3
- package/skills/verify-pixels/SKILL.md +24 -58
- package/skills/build-hud/SKILL.md +0 -38
- package/skills/manage-game-state/SKILL.md +0 -48
|
@@ -1,65 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: override-shader-chunks
|
|
3
|
-
description: Use when a PlayCanvas StandardMaterial needs custom shading its properties cannot express
|
|
3
|
+
description: Use when a PlayCanvas StandardMaterial needs custom shading its properties cannot express, such as stylized looks or vertex displacement, before writing a ShaderMaterial.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Override
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
the clone. For per-mesh uniforms, use `meshInstance.setParameter()` without cloning the material.
|
|
27
|
-
|
|
28
|
-
## Set chunks through the documented accessor
|
|
29
|
-
|
|
30
|
-
Use `material.getShaderChunks(SHADERLANGUAGE_GLSL).set(name, source)`, and the WGSL equivalent —
|
|
31
|
-
the public, documented method — not the underlying `shaderChunks` maps, which are internal. Call
|
|
32
|
-
`material.update()` after any change so cached shader variants are cleared.
|
|
33
|
-
|
|
34
|
-
## Emissive-only overrides
|
|
35
|
-
|
|
36
|
-
An `emissivePS` override implements `void getEmission()`, writing the result into `dEmission`. When
|
|
37
|
-
the override is meant to fully drive the visible color, zero the material's `diffuse`/`specular` and
|
|
38
|
-
disable `useSkybox` so no other lighting term still contributes underneath it.
|
|
39
|
-
|
|
40
|
-
## Uniforms are raw
|
|
41
|
-
|
|
42
|
-
`setParameter` uploads exactly what it is given, with no color-space conversion. Convert any
|
|
43
|
-
authored sRGB color with `Color#linear()` before calling `setParameter`; an sRGB color passed
|
|
44
|
-
straight through renders visibly darker and less saturated than it was authored.
|
|
45
|
-
|
|
46
|
-
## Keep vertex chunks instancing-aware
|
|
47
|
-
|
|
48
|
-
A custom `transformCoreVS` (or any vertex chunk reading the model matrix) must keep the engine's
|
|
49
|
-
`INSTANCING` define path — pulling the per-instance matrix under that define — with a
|
|
50
|
-
non-instanced fallback that uses `matrix_model` directly, matching the stock chunk's structure.
|
|
51
|
-
Dropping this path silently breaks instancing for any mesh instance using the material.
|
|
52
|
-
|
|
53
|
-
## Provide GLSL and WGSL, or pin to WebGL
|
|
54
|
-
|
|
55
|
-
Override both `SHADERLANGUAGE_GLSL` and `SHADERLANGUAGE_WGSL` chunks for anything touched. On a
|
|
56
|
-
WebGPU device, a chunk supplied only in GLSL forces the material's entire shader — not just that
|
|
57
|
-
chunk — onto a GLSL-compiled-then-transpiled path; without the optional transpiler modules loaded
|
|
58
|
-
(the common case), that shader fails to compile. Supply both languages, or explicitly pin the
|
|
59
|
-
graphics device to WebGL if WGSL support is not required.
|
|
60
|
-
|
|
61
|
-
## Expand bounds for vertex displacement
|
|
62
|
-
|
|
63
|
-
If the override displaces vertices, expand the mesh's bounds by the maximum displacement (see
|
|
64
|
-
`apply-conventions`) — CPU-computed bounds never observe shader-side deformation, so culling breaks
|
|
65
|
-
silently otherwise.
|
|
6
|
+
# Override shader chunks
|
|
7
|
+
|
|
8
|
+
Overriding a `StandardMaterial` chunk keeps fog, tone mapping, shadows, skinning, and instancing
|
|
9
|
+
intact. Use a `ShaderMaterial` only when the look needs its own vertex and fragment pair.
|
|
10
|
+
|
|
11
|
+
- Set `material.shaderChunksVersion` to the installed major.minor, such as `'2.22'`. Chunk names
|
|
12
|
+
and contracts change between versions, so read the installed chunk source, not memory.
|
|
13
|
+
- Set chunks with `material.getShaderChunks(SHADERLANGUAGE_GLSL).set(name, source)` and the WGSL
|
|
14
|
+
equivalent, then call `material.update()`. For every material on a device, use
|
|
15
|
+
`ShaderChunks.get(device, language)`; the global `shaderChunks` object is deprecated.
|
|
16
|
+
- Supply both GLSL and WGSL. On WebGPU, one GLSL-only chunk sends the whole shader through a
|
|
17
|
+
transpiler that is usually not loaded, so it fails to compile. Otherwise pin the device to WebGL.
|
|
18
|
+
- Clone shared materials, including imported ones, before overriding them for only some meshes.
|
|
19
|
+
Per-mesh uniforms use `meshInstance.setParameter()`.
|
|
20
|
+
- Uniforms are raw; convert sRGB colours with `Color#linear()`.
|
|
21
|
+
- Vertex chunks that read the model matrix, such as `transformCoreVS`, must keep the `INSTANCING`
|
|
22
|
+
path with a `matrix_model` fallback, like the stock chunk.
|
|
23
|
+
- Expand mesh bounds by the maximum displacement; CPU bounds do not see shader deformation.
|
|
24
|
+
- For an emissive-only look, implement `getEmission()` writing `dEmission`, zero `diffuse` and
|
|
25
|
+
`specular`, and disable `useSkybox`.
|
|
@@ -1,67 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reduce-draw-calls
|
|
3
|
-
description: Use when a PlayCanvas
|
|
3
|
+
description: Use when a PlayCanvas app has too many draw calls or is CPU-bound, before hand-merging meshes or writing custom renderers.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Cut draw calls
|
|
6
|
+
# Cut draw calls
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Measure first with `app.stats.drawCalls.total` or `MiniStats`; the per-pass breakdown fills only in
|
|
9
|
+
profiler builds. Also halve the pixel ratio and call `app.resizeCanvas()`: if frame time drops, the
|
|
10
|
+
cost is fill rate, not draw calls. Stop at the first rung that meets the budget and report
|
|
11
|
+
before-and-after counts.
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
+
## 1. Stop drawing hidden things
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
anything, or drop in `MiniStats` for a live overlay. Every rung below is proved against this number,
|
|
16
|
-
not against intuition about what "looks expensive."
|
|
15
|
+
An element at opacity 0 still draws. Set `enabled = false`.
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
effective pixel ratio (`Math.min(graphicsDevice.maxPixelRatio, window.devicePixelRatio)`) and calling
|
|
20
|
-
`app.resizeCanvas()`. Verify the backbuffer shrank and compare frame times at the same scene state.
|
|
21
|
-
If frame time improves materially, prioritize resolution, multisampling and per-pixel shader cost.
|
|
22
|
-
Otherwise profile CPU and GPU work before choosing a rung; restore the original ratio after the test.
|
|
17
|
+
## 2. BatchManager
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
Call `app.batcher.addGroup(name, dynamic, maxAabbSize)`, then set `batchGroupId` on render, sprite,
|
|
20
|
+
or element components. Use `dynamic: false` for geometry that never moves. Members join when
|
|
21
|
+
enabled in the live tree, in any order, and groups regenerate automatically when membership
|
|
22
|
+
changes. Call `markGroupDirty(id)` only after moving members of a static group. Manual `AppOptions`
|
|
23
|
+
bootstraps must set `batchManager`.
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
layer, so the GPU processes it every frame for no visible result. Toggle `enabled` on the entity to
|
|
28
|
-
actually skip it. Check every element that is ever fully transparent, not only the ones on screen
|
|
29
|
-
when you're profiling — this rung is easy to skip precisely because nothing looks wrong.
|
|
25
|
+
## 3. Hardware instancing
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
Use this for many copies of one mesh and material with per-frame transforms. Build a buffer with
|
|
28
|
+
`VertexFormat.getDefaultInstancingFormat(device)`, one mat4 per instance, and call
|
|
29
|
+
`meshInstance.setInstancing(vb)`; `instancingCount` follows the buffer.
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
There is no per-instance culling. By default the group always draws; `setInstancing(vb, true)`
|
|
32
|
+
culls it as one unit against a `RenderComponent#customAabb` covering every instance. Split large
|
|
33
|
+
instance sets into spatial chunks. Custom vertex chunks need the `INSTANCING` path
|
|
34
|
+
(`override-shader-chunks`). Start from the `graphics/instancing-basic` and
|
|
35
|
+
`graphics/instancing-custom` examples.
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
and that flag requires both the local enable state and hierarchy attachment — set `batchGroupId`
|
|
38
|
-
after the entity is parented into the live tree, not before, or the member silently drops out of the
|
|
39
|
-
group. `BatchManager.generate()` runs once automatically on the app's first rendered frame; if group
|
|
40
|
-
membership changes afterward, call `app.batcher.markGroupDirty(id)` (or `generate([id])`) yourself.
|
|
41
|
-
An Engine-only app without the full `Application` bootstrap must register the class via
|
|
42
|
-
`AppOptions.batchManager` before it can batch anything.
|
|
43
|
-
|
|
44
|
-
## Rung 3: hardware instancing — merge without touching layout
|
|
45
|
-
|
|
46
|
-
Reach for this once distinct materials or per-frame transform updates would defeat `BatchManager`.
|
|
47
|
-
Build a per-instance vertex buffer with `VertexFormat.getDefaultInstancingFormat(device)` (one mat4
|
|
48
|
-
per instance), call `meshInstance.setInstancing(vb)`, and set `instancingCount`. A custom vertex
|
|
49
|
-
chunk needs its own `INSTANCING` code path with an identity-matrix fallback for the non-instanced
|
|
50
|
-
case (cross-reference `override-shader-chunks`).
|
|
51
|
-
|
|
52
|
-
Culling trade-off: instanced meshes cull as one unit against a single bounding volume — an
|
|
53
|
-
off-screen instance inside an otherwise-visible group still draws unless you opt in. Pass
|
|
54
|
-
`setInstancing(vb, true)` and set a `RenderComponent#customAabb` spanning every instance's world
|
|
55
|
-
extent so the renderer has something correct to cull against instead of culling nothing or
|
|
56
|
-
culling the whole group by one instance's bounds.
|
|
57
|
-
|
|
58
|
-
Adapt the official recipes rather than deriving the buffer layout or vertex-shader wiring from
|
|
59
|
-
memory: `graphics/instancing-basic` for the format/buffer contract, `graphics/instancing-custom` for
|
|
60
|
-
the vertex-shader side. Locate both at the installed engine version via `find-examples`.
|
|
61
|
-
|
|
62
|
-
## Prove and report
|
|
63
|
-
|
|
64
|
-
Verify the rendered image is unchanged with `verify-pixels` before calling the change done —
|
|
65
|
-
merging or instancing must not move a single pixel. Re-measure `app.stats.drawCalls` afterward and
|
|
66
|
-
state the before/after counts in the change description; "fewer draw calls" without numbers is not
|
|
67
|
-
a result.
|
|
37
|
+
Confirm the image is unchanged with `verify-pixels`.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reuse-scripts
|
|
3
|
-
description: Use before
|
|
3
|
+
description: Use before writing PlayCanvas behaviour from scratch, to reuse the scripts and helpers the installed Engine already ships.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Engine
|
|
6
|
+
# Reuse shipped Engine code
|
|
7
7
|
|
|
8
|
-
Before writing
|
|
9
|
-
|
|
8
|
+
Before writing behaviour, check what the installed `playcanvas` package already provides.
|
|
9
|
+
|
|
10
|
+
Scripts live under `scripts/esm/**`; import only from there. List them:
|
|
10
11
|
|
|
11
12
|
```sh
|
|
12
13
|
rg 'static scriptName =' node_modules/playcanvas/scripts/esm \
|
|
@@ -14,50 +15,26 @@ rg 'static scriptName =' node_modules/playcanvas/scripts/esm \
|
|
|
14
15
|
| sort
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
handler
|
|
20
|
-
|
|
21
|
-
Only import from `scripts/esm/**`. Legacy sibling directories depend on the global Engine namespace.
|
|
22
|
-
After selecting a script, use `find-examples` to locate its matching versioned Engine example. When
|
|
23
|
-
`node_modules/playcanvas` is a linked or source checkout, its `examples/src/examples/**` are already
|
|
24
|
-
on disk; read them there and skip the fetch.
|
|
25
|
-
|
|
26
|
-
## Check fit before integration
|
|
27
|
-
|
|
28
|
-
Compare candidates with the required behavior, approved art direction, and runtime constraints.
|
|
29
|
-
Reuse a suitable script directly or adapt its configuration and extension points. If none fits,
|
|
30
|
-
state the limitation and implement only the missing behavior, preserving reusable parts and their
|
|
31
|
-
lifecycle, bounds, and input invariants.
|
|
32
|
-
|
|
33
|
-
For visual behavior, resolve an unclear art direction with the user before substantial implementation.
|
|
34
|
-
Present references, mockups, or inexpensive variants; follow existing approval and confirm significant
|
|
35
|
-
departures. A script's default appearance is only a starting point for assessing its fit.
|
|
36
|
-
|
|
37
|
-
## Adapt the reference integration
|
|
38
|
-
|
|
39
|
-
Treat the selected script source and its closest official example as complementary references:
|
|
18
|
+
Common ones are `CameraControls`, `FirstPersonController`, `ThirdPersonController`,
|
|
19
|
+
`ShadowCatcher`, `ProceduralSky`, `Water`, and `Grid`, plus XR and splat scripts. Modules under
|
|
20
|
+
`scripts/esm/parsers` are resource-handler classes, not scripts.
|
|
40
21
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
3. Integrate the selected behavior with its required components, assets, and passes. Adapt the look
|
|
45
|
-
to the approved direction and preserve defaults and dependencies that still apply.
|
|
46
|
-
4. After a rendered frame, fail on console, shader, or missing-asset diagnostics. Exercise the
|
|
47
|
-
behavior with real input where applicable and inspect returned screenshots from representative
|
|
48
|
-
views at the final backbuffer density.
|
|
49
|
-
5. Use the example to check integration correctness and the user's approved direction to judge the
|
|
50
|
-
final look. Report any remaining gap.
|
|
22
|
+
Core exports that are not scripts include `CameraFrame` for post-processing, `Picker`
|
|
23
|
+
(`getSelectionAsync`), `TransformGizmo`, `OutlineRenderer`, `MiniStats`, `GltfExporter`, and
|
|
24
|
+
`UsdzExporter`.
|
|
51
25
|
|
|
52
|
-
|
|
53
|
-
of inventing it from memory.
|
|
26
|
+
## Integrate
|
|
54
27
|
|
|
55
|
-
|
|
28
|
+
1. Read the source for exports, `@attribute` properties, defaults, and required components.
|
|
29
|
+
2. Find its official example with `find-examples`, and copy the assets, layers, scene settings, and
|
|
30
|
+
render setup it depends on.
|
|
31
|
+
3. Adapt configuration and extension points. If nothing fits, implement only the missing part and
|
|
32
|
+
say what was missing.
|
|
33
|
+
4. After a rendered frame, fail on console, shader, or missing-asset errors.
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
Keep a shipped script's input mapping and damping unless asked otherwise, and verify input
|
|
36
|
+
directions with real input rather than deriving signs from memory.
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
[direct Engine](references/direct-engine.md),
|
|
62
|
-
[Web Components](references/web-components.md).
|
|
63
|
-
dependencies alone.
|
|
38
|
+
Grouped property updates differ by surface; preserve defaults you do not change. Read only the
|
|
39
|
+
reference matching the code: [direct Engine](references/direct-engine.md),
|
|
40
|
+
[React](references/react.md), or [Web Components](references/web-components.md).
|
|
@@ -6,9 +6,9 @@ and attach it with `entity.script.create()`. Preserve an existing script compone
|
|
|
6
6
|
Some production helpers are core Engine exports rather than scripts. Follow the matching example's
|
|
7
7
|
construction and teardown contract instead of forcing them through `script.create`.
|
|
8
8
|
|
|
9
|
-
Keep script constructors at module scope. `create` takes two
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Keep script constructors at module scope. `create` takes two option keys: `attributes` is passed to
|
|
10
|
+
the constructor for declared `@attribute` fields, while `properties` is `Object.assign`ed onto the
|
|
11
|
+
instance after construction, overwriting any field it names.
|
|
12
12
|
|
|
13
13
|
Prefer mutating an existing grouped property when only a few fields change; preserve other defaults
|
|
14
14
|
and follow any update or rebuild contract in the selected source.
|
|
@@ -1,70 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: verify-pixels
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when a PlayCanvas rendering change is meant to leave the image unchanged, to prove it with deterministic captures and pixel comparison.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Prove pixels unchanged
|
|
7
7
|
|
|
8
|
-
Choose the gate
|
|
9
|
-
that cover the affected surfaces.
|
|
8
|
+
## Choose the gate
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
- Byte-exact: the change keeps the same decoded data and the same shading math, so every pixel
|
|
11
|
+
must match.
|
|
12
|
+
- Side-by-side review: the change alters sampled values, random order, precision, or where math
|
|
13
|
+
runs. Report it as "not byte-gateable" and show old and new at each pose; do not invent
|
|
14
|
+
thresholds.
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
| --- | --- |
|
|
15
|
-
| Batching, instancing, mesh indexing, chunking, pooling that preserves RNG order, an asset format swap with identical decoded data | Byte-exact matrix |
|
|
16
|
-
| Re-rolled or re-sampled procedural noise, a changed RNG consumption order, math moved between CPU and GPU, changed precision, fewer octaves or samples | Not byte-gateable: side-by-side review |
|
|
16
|
+
## Make frames deterministic
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
- Load every asset and match camera, canvas size, lighting, and animation time between builds. Seed
|
|
19
|
+
incidental randomness.
|
|
20
|
+
- Drive animated shaders from one app-owned time value, not `Date.now()` or `performance.now()`.
|
|
21
|
+
- `app.timeScale = 0` makes `dt` zero, but `update` still runs; anything counting frames or reading
|
|
22
|
+
wall-clock time still advances.
|
|
23
|
+
- Set `app.autoRender = false`, then `app.renderNextFrame = true` before each frame to capture.
|
|
21
24
|
|
|
22
|
-
##
|
|
25
|
+
## Capture and compare
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
- WebGL: `await (device as WebglGraphicsDevice).readPixelsAsync(x, y, w, h, pixels)`. WebGPU:
|
|
28
|
+
render into a `RenderTarget` and read its `colorBuffer` with `Texture#read()`. Avoid colour
|
|
29
|
+
conversion and lossy encoding.
|
|
30
|
+
- Capture the unmodified build twice first. The captures must match exactly, or the harness itself
|
|
31
|
+
is noisy.
|
|
32
|
+
- Cover every touched surface in at least two poses, plus a mid-animation phase where relevant.
|
|
33
|
+
- Report the count, for example "0 of 65536 pixels differ", or the count and where the differences
|
|
34
|
+
are. "Looks the same" is not a result.
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
For both gates, load assets fully and match the scene state, camera, canvas size, lighting, and
|
|
31
|
-
animation time between builds. Seed incidental randomness so only the intended change varies.
|
|
32
|
-
|
|
33
|
-
- Drive every animated shader or vertex effect from one app-owned time value, never `Date.now()` or
|
|
34
|
-
`performance.now()` read inside the render path, so a captured phase is exactly reproducible.
|
|
35
|
-
- Freeze the clock with `app.timeScale = 0` before capturing; nothing should advance between frames
|
|
36
|
-
you did not explicitly step.
|
|
37
|
-
- Step frames explicitly: set `app.autoRender = false` once, then set `app.renderNextFrame = true`
|
|
38
|
-
before each frame you want rendered. The engine renders exactly that frame and clears the flag —
|
|
39
|
-
do not rely on the free-running render loop plus a timed screenshot.
|
|
40
|
-
|
|
41
|
-
## Gate and report the byte-exact class
|
|
42
|
-
|
|
43
|
-
Read the exact backbuffer with `await device.readPixelsAsync(x, y, w, h, pixels)`. In the installed
|
|
44
|
-
engine this method lives on `WebglGraphicsDevice`, so narrow to it; WebGPU needs an equivalent
|
|
45
|
-
readback. An existing capture path must preserve raw pixels without colour conversion or lossy
|
|
46
|
-
encoding before comparison.
|
|
47
|
-
|
|
48
|
-
Before trusting any diff between the old and new build, capture the same pose × phase matrix twice
|
|
49
|
-
from the *unmodified* build. Two captures of identical, frozen state must be bit-identical. If they
|
|
50
|
-
are not, the capture path itself is the source of noise — an unseeded animation, an asset still
|
|
51
|
-
loading, a GPU timing race — and must be fixed before it can say anything about the real change.
|
|
52
|
-
|
|
53
|
-
Byte-compare each pose × phase pair between the two builds; do not diff by looking. Zero differing
|
|
54
|
-
pixels passes outright. Any nonzero diff must be reviewed on-screen, and its cause and extent stated
|
|
55
|
-
in the change description — never merged silently. Report the actual count every time, for example
|
|
56
|
-
"0 of 65536 pixels differ" or "312 of 65536 pixels differ, confined to the object's silhouette edge".
|
|
57
|
-
"Looks the same" or "no visible difference" is not a result.
|
|
58
|
-
|
|
59
|
-
## Review the other class side by side
|
|
60
|
-
|
|
61
|
-
For each planned pose and phase, show the controlled old and new captures together in one image
|
|
62
|
-
for the user's accept or reject. State what differs and why, for example "noise tile replaces
|
|
63
|
-
runtime fbm: pattern period changed, tint and amplitude match". If no difference is visible, report
|
|
64
|
-
that. Skip the cross-build byte comparison for this class.
|
|
65
|
-
|
|
66
|
-
## Keep the harness small
|
|
67
|
-
|
|
68
|
-
Reuse the project's capture harness when available. Keep harness code out of the shipped bundle,
|
|
69
|
-
behind a dev-only import or in a tools directory, and list its files in the change description.
|
|
70
|
-
Capture with a headless browser and return images only at accept-or-reject points, not after every edit.
|
|
36
|
+
Keep harness code out of the shipped bundle.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: build-hud
|
|
3
|
-
description: Use when building a PlayCanvas game's on-screen interface such as ready, pause, and victory overlays, menus, and live indicators like gauges, timers, or reload meters, so the interface is driven by game state, readable over the scene, and separate from the 3D hierarchy.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Game interface
|
|
7
|
-
|
|
8
|
-
A HUD is a view of game state, not a source of it. Render it from the current state each frame or on
|
|
9
|
-
each transition; never store gameplay truth in the DOM or a UI element.
|
|
10
|
-
|
|
11
|
-
## Choose the UI layer
|
|
12
|
-
|
|
13
|
-
- Build the interface in the natural UI layer for the surface — a DOM or React overlay, or the
|
|
14
|
-
Engine `screen` and `element` UI — decided with the `build-app` skill. Keep it outside gameplay
|
|
15
|
-
model hierarchies, as `assemble-scene` requires.
|
|
16
|
-
- For a screen-space Engine UI, adapt a user-interface example with `find-examples` rather than
|
|
17
|
-
guessing element anchoring, scaling, and font handling.
|
|
18
|
-
|
|
19
|
-
## Drive from state
|
|
20
|
-
|
|
21
|
-
- Map each overlay to a game state and each indicator to a snapshot field. A transition shows or
|
|
22
|
-
hides overlays; it does not duplicate the state the game already owns (see `manage-game-state`).
|
|
23
|
-
- An indicator that is idle most of the time toggles `enabled` with its visibility; an element left
|
|
24
|
-
drawing at opacity 0 still costs a draw call every frame (see `reduce-draw-calls`).
|
|
25
|
-
- Anchor and scale elements to the viewport so the layout holds at the target resolution and on
|
|
26
|
-
resize. Verify at the acceptance resolution, not the default window size.
|
|
27
|
-
- Keep copy minimal and legible over the scene: back text with a plate or shadow where the frame is
|
|
28
|
-
bright, and unify interface colour with the scene's grade from `light-scene`.
|
|
29
|
-
|
|
30
|
-
## Prove it reflects state
|
|
31
|
-
|
|
32
|
-
Drive real input through every transition and confirm with screenshots that the correct overlay
|
|
33
|
-
shows and each indicator tracks the snapshot — a reload meter empties and refills, a timer advances,
|
|
34
|
-
a menu appears only in its state. A saved filepath or a visibility flag is not visual evidence;
|
|
35
|
-
inspect the returned pixels as the local agent guide requires.
|
|
36
|
-
|
|
37
|
-
Choose the authoring surface with the `build-app` skill and build the interface through its own UI
|
|
38
|
-
primitives before reaching into the Engine.
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: manage-game-state
|
|
3
|
-
description: Use when structuring a PlayCanvas game's control flow — a small state machine such as ready, playing, paused, and over, pointer-lock capture, pausing on focus or pointer-lock loss, a full reset, and a stable clock and timestep — so the loop stays deterministic and recoverable.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Game state and loop
|
|
7
|
-
|
|
8
|
-
Model the game as a small explicit state machine with one owner of the clock. Every transition names
|
|
9
|
-
its states; behaviour, input, and the interface read the current state rather than a scatter of
|
|
10
|
-
booleans.
|
|
11
|
-
|
|
12
|
-
## States and transitions
|
|
13
|
-
|
|
14
|
-
- Enumerate the states — for example ready, playing, paused, over — and make transitions explicit
|
|
15
|
-
and total. Do not infer state from side effects.
|
|
16
|
-
- Gate the simulation on the active state: advance gameplay, timers, and physics only while playing.
|
|
17
|
-
|
|
18
|
-
## Capture input and pause
|
|
19
|
-
|
|
20
|
-
- Request pointer lock from a user gesture such as a click, never on load. Treat loss of pointer lock
|
|
21
|
-
and tab blur as a pause: freeze the simulation and the clock, and resume on the next gesture.
|
|
22
|
-
- Make `pointerlockchange` idempotent: when `document.pointerLockElement` is the canvas, transition
|
|
23
|
-
ready or paused to playing and leave playing unchanged; only transition playing to paused when the
|
|
24
|
-
element is no longer the canvas. Never implement it as a toggle. Browsers and automation can both
|
|
25
|
-
report an already-acquired lock, and a duplicate enter event must not pause the game.
|
|
26
|
-
- Clamp the per-frame delta before integrating anything, as `build-app` requires, so a backgrounded
|
|
27
|
-
tab or a slow frame cannot inject one large step into movement, cooldowns, or the clock.
|
|
28
|
-
|
|
29
|
-
## Reset
|
|
30
|
-
|
|
31
|
-
- A reset restores every owned system to its ready values — entities, camera, clock, timers,
|
|
32
|
-
cooldowns, effects, and overlays — not only the player. Route reset through the same setup the
|
|
33
|
-
initial state uses, so ready and reset cannot drift apart.
|
|
34
|
-
- Transition to `ready` explicitly before releasing pointer lock. Never derive the reset state from
|
|
35
|
-
`document.pointerLockElement`; pointer lock is an input side effect, not the state machine.
|
|
36
|
-
- Define timer semantics once and keep them literal in state and snapshots. A field named `reload`
|
|
37
|
-
is remaining cooldown time: `0` means ready, firing assigns a positive duration, and the active
|
|
38
|
-
loop counts it down to exactly `0`. Do not expose a normalized readiness value under that name.
|
|
39
|
-
|
|
40
|
-
## Prove the loop
|
|
41
|
-
|
|
42
|
-
Drive the full cycle with real input: enter play, lose pointer lock and confirm the clock and
|
|
43
|
-
simulation freeze, resume, reset and confirm every field returns to its ready value, then reach the
|
|
44
|
-
end state and restart. Read these from the app's own state snapshot, never from console mutation —
|
|
45
|
-
manufactured state is not evidence.
|
|
46
|
-
|
|
47
|
-
Choose the authoring surface with the `build-app` skill and own the loop and lifecycle in the surface
|
|
48
|
-
that creates the application.
|