@jgengine/shell 0.18.0 → 0.18.1

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 (86) hide show
  1. package/CHANGELOG.md +114 -2
  2. package/dist/GamePlayerShell.js +10 -4
  3. package/dist/JoinGate.d.ts +17 -0
  4. package/dist/JoinGate.js +14 -0
  5. package/dist/Shell3dPresentation.d.ts +8 -1
  6. package/dist/Shell3dPresentation.js +34 -5
  7. package/dist/ShellChrome.d.ts +2 -0
  8. package/dist/ShellChrome.js +6 -0
  9. package/dist/TerritoryOverlay.d.ts +21 -0
  10. package/dist/TerritoryOverlay.js +9 -0
  11. package/dist/audio/AudioComponents.js +8 -1
  12. package/dist/audio/audioEngine.d.ts +7 -1
  13. package/dist/audio/audioEngine.js +67 -5
  14. package/dist/camera/cameraRigs.js +7 -0
  15. package/dist/defineGame.js +21 -6
  16. package/dist/devtools/KeysPanel.js +3 -3
  17. package/dist/drivers/FrameDriver.d.ts +5 -1
  18. package/dist/drivers/FrameDriver.js +61 -24
  19. package/dist/drivers/HudOnlyDriver.js +9 -4
  20. package/dist/environment/EnvironmentScene.js +1 -1
  21. package/dist/input/gamepadMerge.d.ts +10 -0
  22. package/dist/input/gamepadMerge.js +12 -0
  23. package/dist/input/gamepadSource.d.ts +12 -0
  24. package/dist/input/gamepadSource.js +107 -0
  25. package/dist/materialOverride.d.ts +4 -1
  26. package/dist/materialOverride.js +10 -1
  27. package/dist/postfx/PostProcessing.d.ts +10 -7
  28. package/dist/postfx/PostProcessing.js +33 -15
  29. package/dist/postfx/gradeShader.js +13 -0
  30. package/dist/render/CascadedShadows.d.ts +11 -0
  31. package/dist/render/CascadedShadows.js +19 -8
  32. package/dist/render/EnvironmentLighting.d.ts +4 -1
  33. package/dist/render/EnvironmentLighting.js +45 -9
  34. package/dist/render/SceneLighting.d.ts +16 -2
  35. package/dist/render/SceneLighting.js +56 -6
  36. package/dist/render/SceneModels.js +45 -9
  37. package/dist/render/modelLoad.d.ts +10 -0
  38. package/dist/render/modelLoad.js +28 -0
  39. package/dist/render/modelRender.d.ts +3 -0
  40. package/dist/render/modelRender.js +6 -2
  41. package/dist/render/testFixtures/proceduralSpriteAtlas.d.ts +15 -0
  42. package/dist/render/testFixtures/proceduralSpriteAtlas.js +30 -0
  43. package/dist/render/testFixtures/proceduralTileLayer.d.ts +13 -0
  44. package/dist/render/testFixtures/proceduralTileLayer.js +30 -0
  45. package/dist/render/useFootIk.d.ts +9 -0
  46. package/dist/render/useFootIk.js +89 -0
  47. package/dist/render/useModelAnimation.d.ts +2 -0
  48. package/dist/render/useModelAnimation.js +163 -107
  49. package/dist/scene/CityRenderer.js +661 -236
  50. package/dist/scene/cityWindowMaterial.d.ts +8 -6
  51. package/dist/scene/cityWindowMaterial.js +80 -21
  52. package/dist/settings/appliedSettings.d.ts +3 -1
  53. package/dist/settings/appliedSettings.js +5 -2
  54. package/dist/terrain/terrainDetailMaterial.js +7 -3
  55. package/dist/useShellMultiplayerSync.d.ts +8 -1
  56. package/dist/useShellMultiplayerSync.js +25 -5
  57. package/dist/visibility/CullingProvider.d.ts +2 -1
  58. package/dist/visibility/CullingProvider.js +7 -2
  59. package/dist/world/GridWorldScene.js +4 -0
  60. package/dist/world/TileLayerRenderer.d.ts +9 -0
  61. package/dist/world/TileLayerRenderer.js +64 -0
  62. package/dist/world/WorldParticles.d.ts +31 -0
  63. package/dist/world/WorldParticles.js +128 -0
  64. package/dist/world/WorldScene.d.ts +2 -1
  65. package/dist/world/WorldScene.js +46 -4
  66. package/dist/world/entityPose.d.ts +4 -0
  67. package/dist/world/entityPose.js +9 -0
  68. package/dist/world/tileLayerInstances.d.ts +11 -0
  69. package/dist/world/tileLayerInstances.js +28 -0
  70. package/dist/worldSync.d.ts +5 -1
  71. package/dist/worldSync.js +25 -4
  72. package/package.json +4 -4
  73. package/skills/ce-handoff/SKILL.md +1 -1
  74. package/skills/game-design/SKILL.md +11 -1
  75. package/skills/jgengine/SKILL.md +2 -0
  76. package/skills/jgengine/api.md +94 -19
  77. package/skills/jgengine/capabilities.md +28 -0
  78. package/skills/jgengine-ui/SKILL.md +8 -0
  79. package/skills/jgengine-ui/api.md +127 -16
  80. package/skills/jgengine-ui/capabilities.md +21 -0
  81. package/skills/jgengine-ui/reference.md +3 -0
  82. package/skills/jgengine-world/SKILL.md +6 -0
  83. package/skills/jgengine-world/api.md +312 -12
  84. package/skills/jgengine-world/capabilities.md +102 -0
  85. package/skills/level-design/SKILL.md +1 -1
  86. package/skills/jgengine-ui/references/visual-scorecard.md +0 -73
@@ -4,6 +4,10 @@
4
4
 
5
5
  Reach for these before hand-rolling. Each row is *the thing you need* → *the primitive that already does it*.
6
6
 
7
+ ## ai-decision-graph — Evaluate serializable selector, sequence, condition, action, and utility AI decisions.
8
+
9
+ - `createDecisionGraphRuntime` (function) · `import { createDecisionGraphRuntime } from "@jgengine/core/ai/decisionGraph"`
10
+
7
11
  ## ai-driver — difficulty-aware chase/route driving step producing throttle/brake/steer for the vehicle sim
8
12
 
9
13
  - `driveStep` (function) · `import { driveStep } from "@jgengine/core/ai/driver"`
@@ -16,6 +20,16 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
16
20
 
17
21
  - `createDriverState` (function) · `import { createDriverState } from "@jgengine/core/ai/driver"`
18
22
 
23
+ ## anim-graph — data-first animation state machine with blend trees, crossfades, layers, and clip events
24
+
25
+ - `createAnimGraphRuntime` (function) · `import { createAnimGraphRuntime } from "@jgengine/core/anim/animGraph"`
26
+
27
+ ## animation — inverse kinematics orient a bone toward a target with constrained yaw and pitch
28
+
29
+ - `lookAt` (function) · `import { lookAt } from "@jgengine/core/anim/ikSolver"`
30
+ - `solveFabrik` (function) · `import { solveFabrik } from "@jgengine/core/anim/ikSolver"`
31
+ - `solveTwoBone` (function) · `import { solveTwoBone } from "@jgengine/core/anim/ikSolver"`
32
+
19
33
  ## area-effect-field — source-following area membership with enter/refresh/leave edges, stacking, and cleanup
20
34
 
21
35
  - `createAreaEffectField` (function) · `import { createAreaEffectField } from "@jgengine/core/world"`
@@ -102,6 +116,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
102
116
  - `registerBuiltinTriggerActions` (function) · `import { registerBuiltinTriggerActions } from "@jgengine/core/scene/authoredTriggers"`
103
117
  - `registerTriggerAction` (function) · `import { registerTriggerAction } from "@jgengine/core/world"`
104
118
 
119
+ ## behavior-actions — register named callbacks for decision graph behavior descriptors
120
+
121
+ - `registerBehaviorActions` (function) · `import { registerBehaviorActions } from "@jgengine/core/world"`
122
+
105
123
  ## behavior-control — pause/disable/resume/seek/inspect behavior instances per entity
106
124
 
107
125
  - `behaviorControl` (function) · `import { behaviorControl } from "@jgengine/core/world"`
@@ -142,6 +160,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
142
160
 
143
161
  - `createCameraShake` (function) · `import { createCameraShake } from "@jgengine/core/world"`
144
162
 
163
+ ## character-controller — capsule walk over the physics backend — slide, step-up, slopes, crouch, platforms
164
+
165
+ - `createCharacterController` (function) · `import { createCharacterController } from "@jgengine/core/movement/characterController"`
166
+
145
167
  ## city-district — polygon math for road-derived blocks, parcels, and buildable footprints
146
168
 
147
169
  - `Vec2` (type) · `import { Vec2 } from "@jgengine/core/world/cityGeometry"`
@@ -279,6 +301,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
279
301
 
280
302
  - `DEFAULT_FORWARD` (const) · `import { DEFAULT_FORWARD } from "@jgengine/core/world"`
281
303
 
304
+ ## free-flight — camera-relative creative/spectator/noclip/hover flight with correct strafe, sprint, and optional gravity
305
+
306
+ - `createFreeFlightController` (function) · `import { createFreeFlightController } from "@jgengine/core/world"`
307
+
308
+ ## gamepad-input — Resolve sampled gamepad input into actions with deadzones and response curves.
309
+
310
+ - `resolveGamepadFrame` (function) · `import { resolveGamepadFrame } from "@jgengine/core/input/gamepadModel"`
311
+
282
312
  ## glide — gliding/wingsuit descent control from a launch
283
313
 
284
314
  - `createGlideModel` (function) · `import { createGlideModel } from "@jgengine/core/world"`
@@ -337,6 +367,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
337
367
 
338
368
  - `generateLock` (function) · `import { generateLock } from "@jgengine/core/world"`
339
369
 
370
+ ## locomotion-graph — build the default idle/walk/run plus one-shot graph from clip names
371
+
372
+ - `locomotionGraph` (function) · `import { locomotionGraph } from "@jgengine/core/anim/locomotionGraph"`
373
+
340
374
  ## map-annotations — player-drawn map annotation layer — freehand strokes, area shapes, and pinned notes, projected into the map's routes/zones props and serializable
341
375
 
342
376
  - `createAnnotationLayer` (function) · `import { createAnnotationLayer } from "@jgengine/core/world"`
@@ -378,6 +412,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
378
412
 
379
413
  - `createOrderRegistry` (function) · `import { createOrderRegistry } from "@jgengine/core/world"`
380
414
 
415
+ ## particle-director — game-reachable VFX seam: queue one-shot particle bursts and keyed follow-capable standing emitters as serializable data for the shell to render
416
+
417
+ - `createParticleDirector` (function) · `import { createParticleDirector } from "@jgengine/core/vfx/particleDirector"`
418
+
381
419
  ## particle-system — deterministic pooled particle emitter with SoA render buffers, burst/continuous emission, gravity/drag, size/color/alpha-over-life, and serializable state
382
420
 
383
421
  - `createParticleSystem` (function) · `import { createParticleSystem } from "@jgengine/core/world"`
@@ -403,6 +441,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
403
441
 
404
442
  - `definePatrolOrder` (function) · `import { definePatrolOrder } from "@jgengine/core/world"`
405
443
 
444
+ ## perception-service — retain sight, sound, and damage observations with range, cone, occlusion, and decay policy
445
+
446
+ - `createPerception` (function) · `import { createPerception } from "@jgengine/core/sensor/perception"`
447
+
448
+ ## physics-backend — one interface over interchangeable rigid-body solvers — bodies, joints, raycast, shapecast, snapshot
449
+
450
+ - `PhysicsBackend` (interface) · `import { PhysicsBackend } from "@jgengine/core/physics/physicsBackend"`
451
+
452
+ ## physics-backend-conformance — the shared test contract a physics adapter runs to prove it matches the seam
453
+
454
+ - `runPhysicsBackendConformance` (function) · `import { runPhysicsBackendConformance } from "@jgengine/core/physics/physicsBackendConformance"`
455
+
456
+ ## physics-world-backend — the zero-dependency PhysicsWorld solver exposed through the PhysicsBackend seam
457
+
458
+ - `createPhysicsWorldBackend` (function) · `import { createPhysicsWorldBackend } from "@jgengine/core/physics/physicsWorldBackend"`
459
+
406
460
  ## piece-sockets-from-model — derive build-piece snap sockets from a model's named attach points
407
461
 
408
462
  - `pieceSocketsFromModel` (function) · `import { pieceSocketsFromModel } from "@jgengine/core/world/buildSockets"`
@@ -451,6 +505,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
451
505
 
452
506
  - `evaluateQteSequence` (function) · `import { evaluateQteSequence } from "@jgengine/core/world"`
453
507
 
508
+ ## rapier-physics-backend — native capsules, shapecasts, rotation, CCD, joints, and collision queries
509
+
510
+ - `createRapierBackend` (function) · `import { createRapierBackend } from "@jgengine/rapier"`
511
+
512
+ ## rate-window-policy — Evaluate deterministic request windows with retry timing.
513
+
514
+ - `decideRateWindow` (function) · `import { decideRateWindow } from "@jgengine/core/time/rateWindow"`
515
+
454
516
  ## reputation — faction standing that crosses named reputation tiers
455
517
 
456
518
  - `tierForStanding` (function) · `import { tierForStanding } from "@jgengine/core/world"`
@@ -560,6 +622,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
560
622
 
561
623
  - `surfaceRing` (function) · `import { surfaceRing } from "@jgengine/core/world"`
562
624
 
625
+ ## tactical-cover-points — derive cover candidates from navigation-mesh boundaries
626
+
627
+ - `coverPoints` (function) · `import { coverPoints } from "@jgengine/core/ai/tacticalQueries"`
628
+
629
+ ## tactical-position-score — rank bounded tactical positions with caller-defined weights
630
+
631
+ - `scorePositions` (function) · `import { scorePositions } from "@jgengine/core/ai/tacticalQueries"`
632
+
563
633
  ## target-acquirer — stateful held-target wrapper over an acquisition policy with retention
564
634
 
565
635
  - `createTargetAcquirer` (function) · `import { createTargetAcquirer } from "@jgengine/core/world"`
@@ -584,6 +654,30 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
584
654
 
585
655
  - `raycastHeightField` (function) · `import { raycastHeightField } from "@jgengine/core/world/terrain"`
586
656
 
657
+ ## territory-cell-chunk — Map ownership cells to persisted world chunks.
658
+
659
+ - `territoryChunkKey` (function) · `import { territoryChunkKey } from "@jgengine/core/world/territory"`
660
+
661
+ ## territory-footprint — Resolve exactly the ownership cells touched by a placement footprint.
662
+
663
+ - `territoryFootprintCells` (function) · `import { territoryFootprintCells } from "@jgengine/core/world/territory"`
664
+
665
+ ## territory-owner-query — Read a loaded land cell owner for authoritative placement checks.
666
+
667
+ - `territoryOwnerOf` (function) · `import { territoryOwnerOf } from "@jgengine/core/world/territory"`
668
+
669
+ ## territory-ownership — Claim, release and query land through host-owned snapshot storage.
670
+
671
+ - `createTerritory` (function) · `import { createTerritory } from "@jgengine/core/world/territory"`
672
+
673
+ ## territory-placement — Purchase footprint cells atomically with object placement.
674
+
675
+ - `placeWithTerritory` (function) · `import { placeWithTerritory } from "@jgengine/core/world/territory"`
676
+
677
+ ## territory-read-scope — Load the footprint and foreign-owner gap without scanning the world.
678
+
679
+ - `territoryChunkKeys` (function) · `import { territoryChunkKeys } from "@jgengine/core/world/territory"`
680
+
587
681
  ## touch-controls — switch the on-screen touch control set when gameplay context changes (enter/exit vehicle, mount, build mode)
588
682
 
589
683
  - `setTouchControlsMode` (function) · `import { setTouchControlsMode } from "@jgengine/core/input/touchControlsMode"`
@@ -601,6 +695,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
601
695
 
602
696
  - `resolveWalkerStep` (function) · `import { resolveWalkerStep } from "@jgengine/core/movement/solidObstacles"`
603
697
 
698
+ ## walker-solid-step-source — slide a step against solid geometry from a pure rules package
699
+
700
+ - `resolveSourceWalkerStep` (function) · `import { resolveSourceWalkerStep } from "@jgengine/core/movement/solidObstacles"`
701
+
604
702
  ## wave-runner — observable serializable wave/spawn runner over the seeded spawn director — WAVE N, wave progress, budget/alert readout, onSpawn sink, snapshot/restore
605
703
 
606
704
  - `createWaveRunner` (function) · `import { createWaveRunner } from "@jgengine/core/world"`
@@ -620,6 +718,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
620
718
  - `buildTrimmedIntersections` (function) · `import { buildTrimmedIntersections } from "@jgengine/core/world"`
621
719
  - `trimBandAtJunctions` (function) · `import { trimBandAtJunctions } from "@jgengine/core/world"`
622
720
 
721
+ ## world-physics-colliders — Synchronize terrain and authored static colliders with a physics backend.
722
+
723
+ - `syncWorldColliders` (function) · `import { syncWorldColliders } from "@jgengine/core/physics/worldColliders"`
724
+
623
725
  ## world-place — declare the place a game happens in — flat/round/voxel/board ground, surface laws, per-place physics
624
726
 
625
727
  - `world` (function) · `import { world } from "@jgengine/core/world"`
@@ -11,7 +11,7 @@ Own the spatial realization of game design: player metrics, topology, routes, la
11
11
 
12
12
  All authorable world content belongs in `editor.scene.json`, changed through the editor GUI or RPC/CLI. Never hand-edit the document or keep geometry, paths, spawns, zones, objectives, terrain, foliage, encounters, or coordinate arrays in code. File an editor `[FEATURE]` issue before any fallback.
13
13
 
14
- Read [references/application-playbook.md](references/application-playbook.md) only when the task explicitly calls for spatial design depth — a level/world design brief, an audit or redesign, or "make this fun/better" work; it is not a mandatory step of creating a game. Read [references/live-editor-loop.md](references/live-editor-loop.md) whenever live captures, an editor-agent handoff, or iterative scene improvement is involved. Read [references/field-guide.md](references/field-guide.md) for metric families, topology, pacing, genre lenses, accessibility, and research.
14
+ For a greenfield build, read [references/application-playbook.md](references/application-playbook.md) and produce its greenfield contract (promise, pillars, anti-pillars, core decision, and falsifier) before writing code. Audits may read only the playbook's audit sections. Read [references/live-editor-loop.md](references/live-editor-loop.md) whenever live captures, an editor-agent handoff, or iterative scene improvement is involved. Read [references/field-guide.md](references/field-guide.md) for metric families, topology, pacing, genre lenses, accessibility, and research.
15
15
 
16
16
  ## Choose the mode
17
17
 
@@ -1,73 +0,0 @@
1
- # Visual scorecard
2
-
3
- This repeatable 0–3 rubric makes "looks shipped" consistent across sessions and usable by a fresh-eyes reviewer. Apply it to milestone shots, not every tweak, and place the scored table beside PR screenshots.
4
-
5
- Structure and thresholds adapted (concept only, reworded for this stack — not copied verbatim) from `majidmanzarpour/threejs-game-skills`' `visual-scorecard.md` (MIT); see `CREDITS.md`.
6
-
7
- ## The 0–3 scale
8
-
9
- Score each category independently against what's actually in the shot, not what the code intends.
10
-
11
- | Score | Meaning |
12
- | --- | --- |
13
- | **0** | Engine defaults or debug output. Untextured primitives, default grey material, flat ambient light, no sky/fog treatment, no authored placement. |
14
- | **1** | Basic styled prototype. Readable and on-genre, but generic — solid-color materials, one light, sparse or untuned `AuthoredScene` content, no distinct silhouettes. |
15
- | **2** | Premium stylized. Authored silhouettes on hero/obstacles, a coherent material system (not one grey PBR everywhere), tuned `environment()` lighting/sky/post, foliage and props placed through the editor read as a real place, not a demo. |
16
- | **3** | Showcase art direction. Everything in 2, plus: no repeated silhouette anywhere in frame, deliberate foreground/midground/background composition, purposeful VFX and motion, a HUD that could not be mistaken for another game. Reads like a store page, not a build. |
17
-
18
- ## The 10 categories
19
-
20
- Score all ten from one `--mode play` shot plus deterministic world/document evidence from `jgengine-verify`. Data presence is a prerequisite, not a substitute for visual judgment.
21
-
22
- 1. **Art direction** — one coherent palette and mood across ground, props, sky, and UI; not three unrelated hue choices stapled together.
23
- 2. **Hero/player** — an authored silhouette and material set the player is meant to be, not a default primitive or a capsule with an emissive glow standing in for a character.
24
- 3. **Obstacles/enemies** — distinct types read apart at a glance; not one mesh reused at different scales/colors.
25
- 4. **Rewards/interactables** — visually distinct from hazards and terrain at a glance, not the same proxy geometry as everything else.
26
- 5. **World/environment** — foreground, midground, and background read as separate layers (props/landmarks near camera, terrain shaping mid-distance, sky/fog treatment at the horizon); not one flat plane running to a bare horizon.
27
- 6. **Materials/textures** — terrain and props carry texture and color variation (`environment({ sculpt, materialRegions })`, catalog GLBs via `jgengine-assets`), not flat untextured solids.
28
- 7. **Lighting/render** — a tuned daylight/sun+hemisphere rig and post stack (per `look: "cinematic"`, #773), not the bare untouched three.js default light.
29
- 8. **VFX/motion** — particle/motion feedback tied to actual gameplay events (hits, pickups, weather), not absent, and not one looping default effect standing in everywhere.
30
- 9. **UI/HUD** — a game-specific `HudCanvas`/`HudPanel` composition with unique art direction; fails automatically if it could pass for a generic admin dashboard **or** for another game shipping stock engine widgets/theme presets unskinned.
31
- 10. **Performance evidence** — the look holds at the scale the game actually runs (`InstancedScatter` density, many entities), not only a hand-posed hero shot with three objects in frame.
32
-
33
- ## Measured-evidence row
34
-
35
- Pixel metrics from `bun run shoot <id> --mode play --inspect` (or `bun run inspect-shot.ts <shot.png>` on an existing PNG — [#788](https://github.com/Noisemaker111/jgengine/issues/788), shipped) are the objective inputs to categories 5–7 — they don't replace the eye, they catch what a rushed glance misses. Output lands at `shots/<name>.metrics.json`:
36
-
37
- | Metric | Threshold | Flags |
38
- | --- | --- | --- |
39
- | `colorEntropyBits` | `< 3.0` | sparse palette |
40
- | `dominantColorShare` | `> 0.6` | sparse / one material dominates the frame |
41
- | `edgeDensity` | `< 0.04` | primitive-dominant, too few silhouettes |
42
- | `luminance.contrast` | `< 60` | fog/post compressing out real geometry |
43
-
44
- Any metric over threshold caps the affected category at **1** regardless of how the shot reads by eye — the number is a floor, not a veto the eye can talk it out of. `--strict` hard-fails on a crossed threshold for CI; the scorecard itself always records this row from the JSON, never "not measured."
45
-
46
- ## Pass thresholds
47
-
48
- - **Premium**: every category ≥ 2, average ≥ 2.3.
49
- - **Showcase**: at least 6 categories at 3, no category < 2, average ≥ 2.7.
50
-
51
- Below "premium" on a claimed milestone is not shippable; keep working the art stack or narrow the claim.
52
-
53
- ## Automatic failures
54
-
55
- Any one of these fails the shot regardless of the averaged score:
56
-
57
- - **Primitive-dominant shots** — untextured boxes/spheres/cylinders make up the visible mass of the frame.
58
- - **Default primitive + glow standing in for a hero** — an emissive-glow capsule/sphere is not a character, even at full brightness.
59
- - **One repeated silhouette everywhere** — `InstancedScatter`'s stylized proxy species (or any single catalog model) used with no palette variety across the whole visible world.
60
- - **Fog/bloom hiding missing geometry** — atmosphere used to obscure an empty or unfinished scene instead of to add depth to a populated one.
61
- - **No active-play shot** — only editor/menu/poster shots submitted. `--mode play` (or `drive` past the menu) evidence is mandatory; a scene that only looks good in the editor camera hasn't proven anything about how the game actually reads.
62
-
63
- ## Fresh-eyes review and the lower-score rule
64
-
65
- Hand a reviewer **only** the screenshots, this rubric, and the measured-evidence JSON — no PR description, no "here's what I was going for." A reviewer who knows the intent scores the intent; a fresh reviewer scores the pixels, which is the only thing a player ever sees.
66
-
67
- - The author scores first (self-score).
68
- - A fresh-eyes pass (a different session, or the same session after clearing framing context) scores independently.
69
- - Where the two disagree, **the PR carries the lower score**, never the average and never the higher one. Optimistic self-scoring is the failure mode this guards against — take the lower number and keep working the art stack instead of arguing the rubric.
70
-
71
- ## Using this in a PR
72
-
73
- Any PR that changes what a player sees pastes the ten-category table (score + one-line reason per category below 2), the measured-evidence row, and the reconciled score next to embedded screenshots. Report that verdict through `jgengine-verify`'s evidence format.