@origingame/engine 0.0.0-stage → 0.1.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 (159) hide show
  1. package/bin/og.mjs +25 -0
  2. package/dist/web/origin-engine.d.ts +1233 -0
  3. package/dist/web/origin-engine.js +8628 -0
  4. package/dist/web/origin-engine_bg.wasm +0 -0
  5. package/dist/web/origin-engine_bg.wasm.d.ts +366 -0
  6. package/dist/web-publish/origin-engine-lanes.js +8509 -0
  7. package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
  8. package/dist/web-publish/origin-engine.d.ts +1176 -0
  9. package/dist/web-publish/origin-engine.js +8313 -0
  10. package/dist/web-publish/origin-engine_bg.wasm +0 -0
  11. package/dist/web-publish/origin-engine_bg.wasm.d.ts +347 -0
  12. package/dist/web-publish/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
  13. package/dist/web-publish-2d/origin-engine-lanes.js +8509 -0
  14. package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
  15. package/dist/web-publish-2d/origin-engine.d.ts +1176 -0
  16. package/dist/web-publish-2d/origin-engine.js +8313 -0
  17. package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
  18. package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +347 -0
  19. package/dist/web-publish-2d/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
  20. package/knowledge/README.md +31 -0
  21. package/knowledge/knowledge.json +13 -0
  22. package/knowledge/looks/catalogue.json +1871 -0
  23. package/knowledge/package.json +33 -0
  24. package/knowledge/rules/platform.json +329 -0
  25. package/knowledge/scaffold/README.md +27 -0
  26. package/knowledge/scaffold/distribution.json +48 -0
  27. package/knowledge/scaffold/origin-engine.json +19 -0
  28. package/knowledge/scaffold/template/.origin/setup +14 -0
  29. package/knowledge/scaffold/template/LICENSES.md +23 -0
  30. package/knowledge/scaffold/template/README.md +37 -0
  31. package/knowledge/scaffold/template/assets/ai/guard.ogt +48 -0
  32. package/knowledge/scaffold/template/assets/audio/click.ogt +9 -0
  33. package/knowledge/scaffold/template/assets/fonts/NotoSansSC-Regular-subset.ttf.ogt +12 -0
  34. package/knowledge/scaffold/template/assets/fonts/OFL.txt +93 -0
  35. package/knowledge/scaffold/template/assets/looks/origin-game.ogt +12 -0
  36. package/knowledge/scaffold/template/assets/nav/yard.ogt +27 -0
  37. package/knowledge/scaffold/template/assets/scenes/main.ogt +34 -0
  38. package/knowledge/scaffold/template/assets/strings/en.ogt +14 -0
  39. package/knowledge/scaffold/template/assets/textures/stone_n.png.ogt +7 -0
  40. package/knowledge/scaffold/template/gitattributes +22 -0
  41. package/knowledge/scaffold/template/gitignore +8 -0
  42. package/knowledge/scaffold/template/package.json +18 -0
  43. package/knowledge/scaffold/template/project.ogt +25 -0
  44. package/knowledge/scaffold/template/src/main.ts +246 -0
  45. package/knowledge/scaffold/template/tests/guard.og.ts +67 -0
  46. package/knowledge/scaffold/template/tests/smoke.og.ts +43 -0
  47. package/knowledge/scaffold/template/tsconfig.json +17 -0
  48. package/knowledge/scripts/lib.ts +207 -0
  49. package/knowledge/scripts/mount.ts +210 -0
  50. package/knowledge/scripts/pack.ts +25 -0
  51. package/knowledge/scripts/scaffold.ts +72 -0
  52. package/knowledge/scripts/validate.ts +749 -0
  53. package/knowledge/skills/og-ai/SKILL.md +386 -0
  54. package/knowledge/skills/og-audio/SKILL.md +191 -0
  55. package/knowledge/skills/og-build/SKILL.md +330 -0
  56. package/knowledge/skills/og-core/SKILL.md +285 -0
  57. package/knowledge/skills/og-feel/SKILL.md +385 -0
  58. package/knowledge/skills/og-game/SKILL.md +1479 -0
  59. package/knowledge/skills/og-i18n/SKILL.md +220 -0
  60. package/knowledge/skills/og-platform-sdk/SKILL.md +199 -0
  61. package/knowledge/skills/og-taste/SKILL.md +537 -0
  62. package/knowledge/skills/og-ui/SKILL.md +444 -0
  63. package/knowledge/wiki/ai.md +627 -0
  64. package/knowledge/wiki/anim.md +1187 -0
  65. package/knowledge/wiki/atmosphere.md +636 -0
  66. package/knowledge/wiki/audio.md +732 -0
  67. package/knowledge/wiki/camera.md +711 -0
  68. package/knowledge/wiki/commands.md +357 -0
  69. package/knowledge/wiki/game.md +220 -0
  70. package/knowledge/wiki/i18n.md +213 -0
  71. package/knowledge/wiki/index.md +1027 -0
  72. package/knowledge/wiki/input.md +1714 -0
  73. package/knowledge/wiki/load.md +1280 -0
  74. package/knowledge/wiki/math.md +166 -0
  75. package/knowledge/wiki/nav.md +1154 -0
  76. package/knowledge/wiki/physics.md +3142 -0
  77. package/knowledge/wiki/platform.md +857 -0
  78. package/knowledge/wiki/probe.md +1692 -0
  79. package/knowledge/wiki/reflect.md +285 -0
  80. package/knowledge/wiki/render.md +3511 -0
  81. package/knowledge/wiki/rng.md +393 -0
  82. package/knowledge/wiki/stream.md +376 -0
  83. package/knowledge/wiki/system.md +197 -0
  84. package/knowledge/wiki/systems.md +43 -0
  85. package/knowledge/wiki/terrain.md +727 -0
  86. package/knowledge/wiki/tilemap.md +422 -0
  87. package/knowledge/wiki/time.md +150 -0
  88. package/knowledge/wiki/transform.md +230 -0
  89. package/knowledge/wiki/tween.md +580 -0
  90. package/knowledge/wiki/ui.md +8000 -0
  91. package/knowledge/wiki/world.md +909 -0
  92. package/package.json +68 -4
  93. package/schema/api.schema.json +29675 -0
  94. package/shell/README.md +22 -0
  95. package/shell/brand/README.md +23 -0
  96. package/shell/brand/icon-192.png +0 -0
  97. package/shell/brand/icon-512.png +0 -0
  98. package/shell/brand/mark-on-paper.svg +6 -0
  99. package/shell/brand/origin-mark.svg +5 -0
  100. package/shell/brand/origin-motion.css +251 -0
  101. package/shell/brand/tokens.css +20 -0
  102. package/shell/brand.ts +132 -0
  103. package/shell/page.ts +715 -0
  104. package/shell/platform.ts +203 -0
  105. package/shell/player.ts +238 -0
  106. package/shell/shell.css +185 -0
  107. package/shell/sw.ts +177 -0
  108. package/shell/transport.ts +515 -0
  109. package/shell/wire.ts +369 -0
  110. package/src/e6.ts +69 -0
  111. package/src/env.d.ts +100 -0
  112. package/src/generated/abi.ts +980 -0
  113. package/src/index.ts +193 -0
  114. package/src/node/audio.ts +86 -0
  115. package/src/node/build.ts +109 -0
  116. package/src/node/device.ts +981 -0
  117. package/src/node/doctor.ts +60 -0
  118. package/src/node/index.ts +102 -0
  119. package/src/node/platform.ts +24 -0
  120. package/src/node/playtest-run.ts +249 -0
  121. package/src/node/png.ts +130 -0
  122. package/src/node/polish.ts +976 -0
  123. package/src/node/run.ts +373 -0
  124. package/src/node/web-build.ts +808 -0
  125. package/src/p2.ts +35 -0
  126. package/src/platform-local.ts +211 -0
  127. package/src/playtest.ts +131 -0
  128. package/src/runtime.ts +1509 -0
  129. package/src/systems/cameras.ts +247 -0
  130. package/src/ui-kit/bind.ts +141 -0
  131. package/src/ui-kit/huds.ts +303 -0
  132. package/src/ui-kit/index.ts +36 -0
  133. package/src/ui-kit/menus.ts +187 -0
  134. package/src/ui-kit/settings.ts +131 -0
  135. package/src/ui-kit/store.ts +222 -0
  136. package/src/ui-kit/story.ts +89 -0
  137. package/src/ui-kit/util.ts +106 -0
  138. package/src/web/a11y.ts +429 -0
  139. package/src/web/audio-worklet.ts +193 -0
  140. package/src/web/audio.ts +307 -0
  141. package/src/web/basis.ts +239 -0
  142. package/src/web/gamepad.ts +123 -0
  143. package/src/web/host.ts +166 -0
  144. package/src/web/input.ts +237 -0
  145. package/src/web/lanes.ts +21 -0
  146. package/src/web/play.ts +20 -0
  147. package/src/web/rpc-ring.ts +110 -0
  148. package/src/web/settle.ts +122 -0
  149. package/src/web/shell.ts +643 -0
  150. package/src/web/text-input.ts +248 -0
  151. package/src/web/worker.ts +1002 -0
  152. package/types/bindings.json +13654 -0
  153. package/types/index.d.ts +20090 -0
  154. package/vendor/basis/LICENSE +201 -0
  155. package/vendor/basis/NOTICE +15 -0
  156. package/vendor/basis/README.md +18 -0
  157. package/vendor/basis/basis_transcoder.js +19 -0
  158. package/vendor/basis/basis_transcoder.wasm +0 -0
  159. package/README.md +0 -3
@@ -0,0 +1,537 @@
1
+ ---
2
+ name: og-taste
3
+ description: Art direction for a game on Origin Engine — choosing a style from the premise (genre, mood, era, audience) out of a 37-style catalogue and binding it as a look (post stack, stylisation, material defaults, skin) with a UI skin, a palette and a type ramp; composition and hierarchy on the play surface, colour, typography, motion, feedback and consistency; atmosphere (fog, sky, time of day, weather, water); the anti-patterns that make a game look like a web form; and the checklist to run (with og run captures and dump_ui) before calling a game done. Use when starting a game's look, building or reviewing any screen or HUD, or when a game "works but looks generic".
4
+ ---
5
+
6
+ A game is judged in its first second, before anyone reads a word. The
7
+ engine's UI is correct by default and characterless by default: the platform
8
+ tokens are grey glass that suits a settings page. This skill is how to give a
9
+ game a look of its own and keep it consistent. The mechanics are og-ui's
10
+ (skins, motion and sounds are og-ui §6) and the amounts of game feel
11
+ og-feel's; this is the taste.
12
+
13
+ ## What works
14
+
15
+ Both games written from this package alone reached `og polish`'s taste 99
16
+ from a preset tuned in their own look file: Lantern Vale
17
+ (`examples/lantern-vale`) the anime cel preset with ink, a tinted shadow
18
+ side and a painted second look for the results; Hollowbell
19
+ (`examples/hollowbell`) a 16-bit pixel grid with its own 32-colour palette
20
+ through `Stylise.palette` and a Bayer dither, and the 2D glow. Swapping the
21
+ look at a tick (`og.render.applyLook`) for the results worked the first
22
+ time in both. The Kestrel Stair (`examples/kestrel-stair`, 3D
23
+ third-person) reached taste 98 with a look extending `watercolor-wash`
24
+ (AgX, bloom, a violet shadow tint, a pale fog, the synthetic sky) under
25
+ `og.DayNight`, and `luminous-layered-painting` applied at a tick for the
26
+ ending and the results.
27
+
28
+ ## 1. Choose a style from the premise
29
+
30
+ Write the premise in one line, then answer four questions:
31
+
32
+ | Question | Pulls toward |
33
+ |---|---|
34
+ | **Genre**: what does the player do most? | action and arcade → bold, high contrast, fast motion; puzzle and idle → soft, readable, gentle motion; strategy and RPG → dense, ornamented, calm |
35
+ | **Mood**: how should a session feel? | tense → dark ground, one hot accent; cosy → warm light ground, rounded; eerie → desaturated, thin type |
36
+ | **Era or world**: when and where is it set? | 8- and 16-bit nostalgia → pixel; myth, castles, quests → fantasy; space, hacking, mechs → sci-fi; city nights, synthwave, rhythm → neon; everyday, notebooks, mysteries → paper |
37
+ | **Audience**: who plays, on what? | children and casual phone players → cartoon (big targets, bright, bouncy); core desktop players → any, but denser |
38
+
39
+ Then choose the **look** from the style catalogue (§12): match the four
40
+ answers against each style's `premise` (genre first, then mood, then era
41
+ and audience), shortlist two, and take the one whose three `checks` the
42
+ game can hold on every frame. Some premises and where they land:
43
+
44
+ | Premise | Look (`extends`) | Why |
45
+ |---|---|---|
46
+ | a brisk 2D platformer, 16-bit nostalgia | `16bit-pixel` | energetic and bright: a 224-line grid, eight tones, a Bayer dither |
47
+ | a cosy farming or life sim seen from above | `topdown-tile-pixel` | cosy and curious: a tile-friendly grid, flat crisp tiles |
48
+ | a calm city or diorama builder | `miniature-diorama` | tilt-shift focus, a tabletop model |
49
+ | an exploration or flight game, serene | `faceted-low-poly` | flat facets, a soft vignette, AgX light |
50
+ | a fighting or character-action game | `anime-cel-3d` | toon bands, cool-tinted shadows, outlines that thin with distance |
51
+ | survival horror, lonely and uncanny | `low-resolution-3d` | early-console 3D: snapped vertices, affine textures, fog |
52
+ | an arcade shooter, rhythm or racing game | `neon-vector` | bright outlines that bloom on a dark scene; a large ground plane keeps its outline only where it ends, and with `outlineDistance` a line grows at most four times its width near the camera |
53
+ | a narrative puzzle, gentle and sad | `watercolor-wash` | bleeding strokes pooling at their rims on paper |
54
+
55
+ `npm create @origingame/game -- --premise "<one line>"` does this match
56
+ for a new game and writes the result (`--look <slug>` names it instead):
57
+ `assets/looks/<game>.ogt` extending the preset, named by `GameDesc.look`,
58
+ with a README section on changing it. For a game that exists, write the
59
+ same file by hand (§12) and add `look: "looks/<game>.ogt"` to `og.game`.
60
+ The entry also names the palette rules, the asset specs and the skin pack
61
+ that matches it. Then pick **one** starting skin. The six packs in the engine's
62
+ `templates/ui-kit/assets/skins/` are starting points, not the only looks:
63
+
64
+ | Pack | Premises it fits | Signature | Start look (§12) |
65
+ |---|---|---|---|
66
+ | `pixel` | platformers, roguelikes, retro shooters, jam games | square frames, 2 px bevels, hard shadows, a bitmap face | `16bit-pixel` |
67
+ | `cartoon` | casual, match-3, party, kids, idle | thick outlines, cream panels, squash on press, pops | `bold-comic-illustration` |
68
+ | `fantasy` | RPGs, card battlers, quest and dungeon games | parchment, gold corners and flourishes, a serif display | `illuminated-manuscript` |
69
+ | `scifi` | space, tactics, hacking, mech | hairlines, cut corners, brackets, scanlines, mono numerals | `cinematic-realism` |
70
+ | `paper` | narrative, puzzle, cosy, mystery, word games | off-white sheets, ink outlines, soft shadows, one red | `pencil-hatching` |
71
+ | `neon` | arcade, rhythm, racing, synthwave | dark glass, glowing tubes, italic display | `neon-vector` |
72
+
73
+ ```ts
74
+ const skin = og.load.skin(world, "skins/fantasy/skin.ogt");
75
+ if (skin.ok) og.ui.configure(world, { skin: skin.value });
76
+ ```
77
+
78
+ Make it the game's own by changing the skin file, never by passing colours
79
+ to individual calls: copy the pack directory into the game's
80
+ `assets/skins/<game>/` (`cp -R .../skins/fantasy assets/skins/<game>`) and
81
+ load `skins/<game>/skin.ogt`. Nothing in it needs rewriting: the ids in a
82
+ skin file are read relative to the file first (`atlas "atlas.png"`, `body
83
+ "Newsreader-Medium.ttf"`), then from `assets/` (`skins/fantasy/atlas.png`
84
+ still works). The one asset outside the pack is its CJK `fallback`,
85
+ `fonts/NotoSansSC-Regular-subset.ttf` (2.5 MB, shared by every pack, so
86
+ not copied six times): a project made by `npm create @origingame/game`
87
+ has it at that id; otherwise point the line at the game's own CJK font,
88
+ or delete it when the game shows no Chinese, Japanese or Korean (the cook
89
+ names the line it cannot find). Then change `palette` (start with
90
+ `accent`, `bg`, `surface`, `fg`), `ramp` and `fonts` (any OFL font beside
91
+ the skin file), and redraw the atlas if the frames should change (`og skin
92
+ pack`, og-ui §6). The art in the art direction of the
93
+ world (sprites, backgrounds) and the skin must agree: a pixel-art world in a
94
+ neon UI reads as two games.
95
+
96
+ ## 2. Composition and hierarchy
97
+
98
+ - **What the eye reads first**: decide the one thing per screen (the score on
99
+ results, Play on the title, the health on a HUD) and make it the biggest,
100
+ the brightest or the only accent-coloured thing. If two things compete,
101
+ shrink one.
102
+ - **The play surface is the hero**: a HUD sits in the corners and edges
103
+ (anchors `topLeft`, `topRight`, `bottom`), never across the middle. Keep
104
+ the centre third free during play.
105
+ - **HUD weight**: at most three HUD groups visible at once; each is a capsule
106
+ with an icon and a number, not a panel with labels. Move rarely needed
107
+ information behind pause.
108
+ - **Safe area**: everything interactive inside `og.ui.frameViewport().safeArea`
109
+ plus the density gutter; anchors do this for you, absolute positions do not.
110
+ - **Phone vs desktop**: on `phonePortrait` the thumb zone is the bottom third:
111
+ primary actions go there, touch controls at least 44 CSS px apart; titles
112
+ shrink one step (`displayMd` instead of `displayLg`). On desktop, menus sit
113
+ centred or left, never full-width.
114
+ - **Grouping**: spacing does the grouping. Related things are `space.sm`
115
+ apart, groups `space.lg` or more. Do not draw a box around everything.
116
+
117
+ ## 3. Colour
118
+
119
+ - **Limited palette**: a ground (`bg`), one or two surfaces, one ink (`fg`)
120
+ with a muted ink, **one accent**, and the status colours. That is the whole
121
+ palette; a skin declares exactly these.
122
+ - **One accent**, used for the one primary action per screen, earned stars,
123
+ the player's own row and focus. If everything is accent, nothing is.
124
+ - **Value before hue**: check the screen in greyscale. Text, the primary
125
+ button and the player character must separate from their ground by value
126
+ alone (body text at least 4.5:1; the packs' test enforces it for theirs:
127
+ every label ink, every button label in every state, a text field's
128
+ placeholder, which draws in `fgSubtle` on the field's frame, and an
129
+ accent badge, `accentInk` on `accentSoft` over a panel (the results
130
+ screen's "New best"), and a status badge in each of `success`, `warn`,
131
+ `danger` and `info` (the tone's own ink on its 16 % tint), so a copied
132
+ pack whose `fgSubtle`, `fill`, `accentInk`, `accentSoft` or a status
133
+ colour moves must keep those pairs at 4.5:1 too. The status colours are
134
+ also the meters', toasts' and status dots' colours: on a cream panel they
135
+ are deep (the cartoon pack's warn is `#7b5102`, not a bright amber that
136
+ read 1.63:1), on a dark one light (a red under a dark pack's panel is a
137
+ salmon such as `#ff8080`). Make them for the panels; a badge placed on
138
+ the other side of the scale (a light pack's badge straight on its dark
139
+ stage) moves its own ink towards white or black until it reads there,
140
+ and the neutral badge does the same on its `fill`, so neither needs a
141
+ second palette. A game's copied skin is held to the same bars: the
142
+ engine's `pack_contrast.rs` measures the archetypes' and examples'
143
+ skins too, and their fixes were value, not hue (pathguard's lantern
144
+ orange deepened under a cream label, sparkorb's gold to a burnished
145
+ amber under its cream ink, lampdive's plum fill darkened under its
146
+ placeholder, Lantern Vale's status colours taken back to the fantasy
147
+ pack's deep ones);
148
+ it also reads the primary and secondary labels from a rendered frame,
149
+ where thin strokes may never reach their colour: a pixel font's one-pixel
150
+ strokes fall between pixels and took half their ink, so the pixel pack
151
+ thickens them with `textWeight body 0.5` and cooks DotGothic16 at
152
+ `px_range 8`; a copy of the pack made before that needs both lines).
153
+ - Saturated colour on large areas tires the eye: keep saturation for small
154
+ things (the accent, icons, effects) and keep grounds calmer.
155
+ - Status colours mean status: never use `danger` red as decoration.
156
+
157
+ ## 4. Typography
158
+
159
+ - **Display vs body**: a display face for titles, the score and big numbers
160
+ (characterful: serif, rounded, italic, pixel), a body face for everything
161
+ read (plain, open, legible at 13–15 px). Never the display face for
162
+ paragraphs.
163
+ - **Numerals**: scores, timers, prices and stats in the numeric role
164
+ (`font: "mono"`), so digits do not jitter as they count; big scores in the
165
+ display face. Count them with `og.ui.counter`, never by setting text.
166
+ - **A ramp, not sizes**: use the skin's steps (`displayLg`, `xl`, `body`,
167
+ `meta`); three or four steps per screen at most. Each step carries its
168
+ tracking (display sizes tighten, as the tokens do; a skin's `ramp` line
169
+ takes it as its fourth number after the CJK leading), and it is applied
170
+ to Latin text only.
171
+ - Headings get `heading: true` so the skin's flourish frames them.
172
+
173
+ ## 5. Motion
174
+
175
+ - Every change of state moves; nothing pops in or out without a transition
176
+ unless reduced motion is on.
177
+ - **Durations 120 to 300 ms** for UI (the skin's `durations`), up to 700 ms
178
+ for a screen transition, longer only for rewards. **Never linear**: ease
179
+ out on arrival, ease in on exit.
180
+ - **Anticipation and follow-through**: a press squashes before the action
181
+ (`press` in the skin), a reward overshoots and settles (`pop`, `motion.pop`).
182
+ - **Stagger**: a panel's controls arrive one after another (`motion.stagger`
183
+ 30–60 ms), not all at once.
184
+ - **Screens change through a transition** (`og.ui.screenTransition`): fade
185
+ for calm games, wipe or slide for brisk ones, iris for arcade and retro,
186
+ dissolve for dreamlike ones. One kind per game.
187
+ - Respect `reducedMotion` (the settings template passes it): everything
188
+ becomes a fade; do not add motion that ignores it.
189
+
190
+ ## 6. Feedback
191
+
192
+ Every input is answered **on the same tick** (og-feel §1) by a visual and a
193
+ sound: hover (the skin's lift or highlight), press (squash, the `press`
194
+ sound), success (a toast, a burst, `success`), refusal (a disabled button
195
+ shakes and plays `error` by itself; a wrong answer or a locked door shakes
196
+ its element with `og.ui.shake` and the `error` sound; never silently ignore
197
+ a press, and never shake the camera for it). Hand the UI its sounds with
198
+ `og.ui.configure(world, { sounds: { hover, press, open, close, success, error } })`
199
+ or the skin's `sound` lines. Results land with a count-up, stars with a
200
+ `og.ui.burst`, rewards with a pop.
201
+
202
+ ## 7. Consistency
203
+
204
+ - **One skin per game.** Every screen, dialog and HUD uses it.
205
+ - **One radius scale**: the skin's `radius`; never pass radii by hand.
206
+ - **One shadow direction**: the skin's shadows all fall the same way (down);
207
+ in-world shadows should agree.
208
+ - **One icon set**: the skin's `icons`; do not mix icon styles.
209
+ - **One transition kind** for screen changes, one entrance for dialogs.
210
+
211
+ ## 8. Atmosphere
212
+
213
+ Fog, sky, time of day and weather set the mood of a 3D (and 2D) scene more
214
+ cheaply than any model; use them on purpose, one look per area.
215
+
216
+ - **Fog is depth**: distance fog with `density` 0.005–0.02 per metre makes
217
+ the far plane recede; keep the nearest 10 m clear (`start` 6–10). Height
218
+ fog (`heightDensity` 0.03–0.1 below the ground's height) fills valleys and
219
+ swamps. Match the fog's `color` to the sky's horizon or the scene floats.
220
+ - **One sky per area**: a clear noon (`og.Sky` zenith deep blue, horizon
221
+ pale), an overcast grey (low contrast, `sunSize` 0), a warm dusk with
222
+ long shadows (`og.DayNight` at 16.5–17.25 h: at the default `tilt` the
223
+ sun is 20° to 10° up, its light turning warm from about 17 h). Later the
224
+ sun's light on level ground sinks into the sky's and its shadows fade
225
+ (17.5 h: under 7° up), and at 18 h it sets whatever the `tilt` (which
226
+ only lowers the noon sun); 17.5–18.2 h is a glowing horizon without
227
+ sun shadows. Relief shadows the play area sooner: a valley's western
228
+ ridge puts the floor in shade by about 17 h, so check the hours on a
229
+ frame of the real ground (turn `azimuth` to set the sun across the
230
+ valley). A day-night cycle wants `speed` slow enough
231
+ that a player notices it over minutes (0.02–0.05 game hours per second),
232
+ never per encounter.
233
+ - **Weather is a beat, not wallpaper**: rain or snow for a level's mood or a
234
+ story moment; `intensity` 0.5–1; dust for deserts; mist for mornings and
235
+ graveyards. Keep the play area readable: if enemies vanish in the fog,
236
+ lower the density.
237
+ - **Water reads by its edges**: foam where it meets rocks and posts
238
+ (`foamWidth` 0.2–0.5), a shallow colour close to the bed's and a deep one
239
+ dark and saturated; `waveStrength` 0.2–0.5 for ponds, 0.6–1 for seas.
240
+ - **2D**: a `Water2D` lip a shade lighter than the surface; metaball fluids
241
+ in one bright colour with a lighter `rim`, drawn under the foreground.
242
+ - Prove it: captures at the area's time of day; the far third of the frame
243
+ lower in contrast than the near third, and the HUD unchanged by the fog.
244
+
245
+ ## 9. Anti-patterns: "it looks like a web form"
246
+
247
+ Each of these is a defect, not a style choice:
248
+
249
+ - grey rounded rectangles on a grey ground (the token look left as is);
250
+ - the default sans everywhere, display text at body weight;
251
+ - a centred stack of equal buttons with no primary;
252
+ - no ornament, no texture, no shadow: flat boxes floating on the game;
253
+ - no motion: screens and dialogs pop in, numbers jump;
254
+ - no sound: buttons are silent;
255
+ - the HUD as a toolbar: a full-width bar of labelled boxes across the top;
256
+ - labels instead of icons on the HUD ("Health: 72/100" instead of a heart
257
+ and a bar);
258
+ - every colour saturated, or every button in the accent;
259
+ - a settings-page layout (left-aligned form rows) for a title screen.
260
+
261
+ ## 10. The checklist before a game is done
262
+
263
+ Run each item; each is checkable from a scripted run.
264
+
265
+ 1. **A skin is configured**: `og.ui.theme(world).skin` is not empty (log it
266
+ from a command, or read it in a playtest through a `facts` field).
267
+ 2. **Title, pause and results screens captured**: `og run --headless --commands
268
+ "0.5:show:title;1.5:screenshot:title.png;…"` (with the game's own commands);
269
+ look at each capture in greyscale: the one thing per screen reads first.
270
+ 3. **One primary per screen**: in a `dump_ui` capture, at most one button per
271
+ screen uses the primary variant (the template's first item, or the one
272
+ you marked); every other is secondary or quiet.
273
+ 4. **HUD in the corners**: on the gameplay capture, `dump_ui` shows every HUD
274
+ element's rectangle outside the central third of the surface, inside the
275
+ safe area.
276
+ 5. **Touch targets**: run with the phone viewport (`og.ui.previewViewport`,
277
+ `og run --size 1170x2532`) and check in `dump_ui` that every control is at
278
+ least 44 × 44 CSS px and nothing leaves the safe area.
279
+ 6. **Text legible**: body text at 13 px or more in `dump_ui` heights (a line
280
+ of at least 16 px); the skin passes 4.5:1 (the packs' test, or the same
281
+ check over your atlas).
282
+ 7. **Motion present**: captures 100 ms apart after `show:` differ (an
283
+ entrance), and a score `dump_ui` text read twice during the results
284
+ differs, then settles on the final value.
285
+ 8. **Screen changes transition**: a capture halfway through a screen change
286
+ shows the cover (`og.ui.screenTransition` answers `covering` or
287
+ `revealing`).
288
+ 9. **Sound on input**: after a scripted press, `dump_world` lists an
289
+ `og.AudioSource` on the ui bus (bus 3).
290
+ 10. **Reduced motion**: with `reducedMotion: true` the captures show fades
291
+ only, and nothing is missing.
292
+ 11. **CJK**: switch the language to Chinese in settings and capture: no
293
+ missing-glyph boxes (the skin's fallback font covers it).
294
+ 12. **None of section 9** is visible in any capture.
295
+ 13. **`og polish .` scores taste 90 or more** where it applies, and every
296
+ finding left is one you read in the frames and can name a reason for
297
+ (og-build §5).
298
+
299
+ ## 11. Polish, measured: `og polish`
300
+
301
+ `og polish .` scores the measurable part of §10 (contrast, text size, HUD
302
+ occupancy, screen transitions, UI sounds, locale keys) from the frames, the
303
+ UI trees and the run's audio, with og-feel's measures beside it; og-build §5
304
+ is the one list of its measures, thresholds and fixes. The numbers cannot
305
+ judge a style: read the frames before changing anything, and fix the cause
306
+ in the skin or the template, never per call.
307
+
308
+ ## 12. Style catalogue: looks
309
+
310
+ A style is chosen once per game and then held by data, not by habit: the
311
+ **look** (`assets/looks/<game>.ogt`, an `.ogt` of `type look`) binds the
312
+ frame's whole treatment (tone mapping and exposure, the post stack, the
313
+ stylisation primitives, material defaults such as toon shading and
314
+ outlines, fog and sky, the UI skin and the antialiasing) in one diffable
315
+ file. The engine ships the primitives and the presets; the taste is in
316
+ which one you pick and how you tune it.
317
+
318
+ **Pick from the catalogue.** `looks/catalogue.json` (beside this skill)
319
+ describes 37 styles in four families (pixel and retro, paint and line,
320
+ shape and material, 3D rendering). For each: a one-line descriptor, three
321
+ checks a critic can judge on a captured frame, the premises it fits
322
+ (genre, mood, era, audience, as §1 asks them), the preset it maps to, the
323
+ skin pack, palette constraints, asset specs (logical resolution, sprite
324
+ scale, face budget, outline width, texture density) and an art-bible
325
+ direction. Match the premise's four answers against `premise`, shortlist
326
+ two, and choose by the checks you can actually hold for the whole game.
327
+
328
+ **Read `look.support` before promising a style.** `full`: every primitive
329
+ the style needs draws today. `partial` and `planned`: the frame will lack
330
+ what `missing` names until those primitives land; say so to whoever asked
331
+ for the style, and either accept the gap or pick a neighbour. `content`:
332
+ the look lives in authored art (painted backdrops, frame-by-frame
333
+ animation, hand-placed pixels); the engine frames it with the look, but
334
+ the assets carry it, so budget for them.
335
+
336
+ **Write the look.** Start from the style's preset and change only what the
337
+ game needs; your file wins field by field. A style whose `look.presetReady`
338
+ is false has no preset in this engine yet: leave out `extends` and set the
339
+ blocks yourself from the entry's `primitives`.
340
+
341
+ ```text
342
+ type look
343
+ version 1
344
+ ---
345
+ name "Harbour at dusk"
346
+ extends "anime-cel-3d" # the preset's slug (templates/looks/<slug>.ogt)
347
+ skin "skins/harbour/skin.ogt" # the game's own copy of the style's skin pack
348
+ RenderSettings {
349
+ toneMapping agx
350
+ exposure 1.1
351
+ }
352
+ MaterialStyle {
353
+ shadowTint "#3b3566"
354
+ shadowTintAmount 0.6
355
+ }
356
+ Fog {
357
+ enabled true
358
+ color "#9aa7c7"
359
+ density 0.012
360
+ }
361
+ ```
362
+
363
+ **The pixel grid** (`Stylise.pixelHeight`, `pixelUpscale`) scales the
364
+ frame up by a whole number, so pick the height for the output you ship
365
+ at. The height sets the scale: `pixelUpscale` when height × scale fits
366
+ the output's height, else (and for 0) the largest that does. The width
367
+ is the aspect's columns, at most the output's width over the scale, so
368
+ at most the last column gives way, never the scale: 240 lines at 1280 ×
369
+ 720 are 426 columns at 3x (1278 × 720, a 1-pixel border each side), at
370
+ 1920 × 1080 427 columns at 4x (1708 × 960, a 106/60-pixel border). A
371
+ height that divides the output fills it: 180 (4x), 240 (3x) or 360 (2x)
372
+ at 720p; 270 (4x) or 360 (3x) at 1080p. The `16bit-pixel` preset's 224
373
+ lines draw at 3x inside a 43/24-pixel border at 720p. Size the camera to
374
+ the grid: with 16-pixel tiles, 240 lines are 15 tiles tall.
375
+
376
+ The engine ships canvases and palettes a look names without the game
377
+ owning a copy: `Stylise.paintCanvas "@canvas/paper"` (also `linen`, a
378
+ woven canvas, and `watercolor`, cold-pressed paper) and `Stylise.palette
379
+ "@palettes/handheld4"` (also `lcd2`, `dusk16`, `bright32` and `1bit`). The
380
+ cook puts the ones a look names into the bundle; a palette or canvas of
381
+ the game's own is a path under `assets/` as before.
382
+
383
+ **Fit the pixel grid to the screens.** `Stylise.pixelHeight` sets the
384
+ whole-number scale from the output's height alone, the width follows the
385
+ aspect, and what the grid does not cover is a border in
386
+ `Camera.background` (the rule is `Stylise.pixelHeight`'s doc, og-game
387
+ §15). So choose the height by the screens the game meets, not by the era
388
+ alone. The scale per output (✓ fills it, ≈ fills it but for one column, b
389
+ leaves a border):
390
+
391
+ | `pixelHeight` | 1280×720 | 1920×1080 | 2560×1440 | 3840×2160 | phone 1600×720 | phone 1688×780 | phone 1830×824 |
392
+ |---|---|---|---|---|---|---|---|
393
+ | 90 | 8 ✓ | 12 ✓ | 16 ✓ | 24 ✓ | 8 ✓ | 8 b | 9 b |
394
+ | 135 | 5 b | 8 ✓ | 10 b | 16 ✓ | 5 b | 5 b | 6 b |
395
+ | 144 | 5 ✓ | 7 b | 10 ✓ | 15 ✓ | 5 ✓ | 5 b | 5 b |
396
+ | 160 | 4 b | 6 b | 9 ≈ | 13 b | 4 b | 4 b | 5 b |
397
+ | 180 | 4 ✓ | 6 ✓ | 8 ✓ | 12 ✓ | 4 ✓ | 4 b | 4 b |
398
+ | 192 | 3 b | 5 b | 7 b | 11 b | 3 b | 4 b | 4 b |
399
+ | 216 | 3 b | 5 ✓ | 6 b | 10 ✓ | 3 b | 3 b | 3 b |
400
+ | 224 | 3 b | 4 b | 6 b | 9 b | 3 b | 3 b | 3 b |
401
+ | 240 | 3 ≈ | 4 b | 6 ≈ | 9 ≈ | 3 ≈ | 3 b | 3 b |
402
+ | 270 | 2 b | 4 ✓ | 5 b | 8 ✓ | 2 b | 2 b | 3 b |
403
+ | 360 | 2 ✓ | 3 ✓ | 4 ✓ | 6 ✓ | 2 ✓ | 2 b | 2 b |
404
+
405
+ - 180 is the height to reach for (90 or 360 for a coarser or finer grid):
406
+ it fills every 16:9 size. 144 suits 720p and 1440p, 216 and 270 1080p
407
+ and 4K; 240 fills 720p but leaves a 106/60-pixel border at 1080p.
408
+ - 224 (`16bit-pixel`) and 192 (`8bit-pixel`) leave a border everywhere:
409
+ write `pixelHeight 180` (or 216) in the game's look, which wins over the
410
+ preset field by field, unless the border is part of the look.
411
+ - A phone's output is its page's CSS size times the device pixel ratio,
412
+ capped at 2 (`publish { dpr_cap 2 }` in project.ogt; the columns above
413
+ are 800×360, 844×390 and 915×412 CSS pixels in landscape), so most
414
+ phones leave a border whatever the height. Give it a colour of the
415
+ palette (`Camera.background`), or letterbox the page to 16:9
416
+ (`publish { fit letterbox aspect "16:9" }`).
417
+ - With 16-pixel tiles, 180 lines are 11¼ tiles tall: size the camera and
418
+ the rooms to the grid, not the other way round.
419
+ - Prove it on frames: `og run . --headless --size 1280x720` and again at
420
+ `1920x1080`, a screenshot each, and read the border.
421
+
422
+ With a moving 3D camera, lay the paper and the strokes on the scene
423
+ rather than over the screen: `Stylise.paintCanvasSpace surface` (grain
424
+ per metre by `paintCanvasWorldScale`), `hatchingSpace surface` and
425
+ `halftoneSpace surface` stick to the surfaces and wrap round forms, so
426
+ nothing slides over the world as the camera moves; `screen` (the
427
+ default) suits 2D, fixed cameras and a deliberate printed-sheet feel.
428
+ When things move (characters, vehicles, a turning prop), take
429
+ `hatchingSpace object` (and `halftoneSpace object`, `paintCanvasSpace
430
+ object`): the marks are laid in each object's own space, so a walking
431
+ figure carries its strokes instead of wading through them (terrain keeps
432
+ the world's). `hatchingDepth` (0 to 1) gathers the strokes along
433
+ silhouettes and creases as `inkDepth` inks them: the contour shading of
434
+ an inked page. Pixel sprites standing in a 3D set take
435
+ `Sprite.dropShadow`; on a deck or a step give them `dropShadowHeight`,
436
+ and over an og.Terrain their shadows follow the slope, and bend into a
437
+ hollow or over a ridge narrower than themselves, by themselves.
438
+
439
+ Depth bands (`Stylise.depthBands`, the layered-silhouette, black-figure
440
+ and luminous-layered presets) cut the frame by distance from the camera,
441
+ so they need a camera that looks *across* the world: a side-scroller, a
442
+ stage, a three-quarter view down a valley or a street, a high view over
443
+ a landscape, where near, middle and far ground lie at clearly different
444
+ depths. Set `depthBandsNear` where the nearest layer you want starts and
445
+ `depthBandsFar` where the farthest layer ends (the far colour begins one
446
+ band before it), measured along the view on a real frame. They do not
447
+ work top-down or in a steep overhead camera: the whole play area sits at
448
+ about one depth and falls in one band, so it goes one flat colour and
449
+ the characters vanish into it; use a tinted shadow side, fog or a
450
+ palette there instead, or the bands only for a high establishing shot
451
+ (a title, the results). `depthBandsFlatten` 1 gives flat cut-paper
452
+ layers; 0 keeps the lit scene and only tints each layer (its shading and
453
+ hues show through), so characters stay readable; 0.3–0.6 is a painted
454
+ middle ground.
455
+
456
+ Name it in the game (`og.game({ look: "looks/harbour.ogt", … })`) and it
457
+ is applied after the first scene spawns and before `start` runs (so
458
+ `start` may still change any field, and reads the look's skin from
459
+ `og.render.look(world)?.skin`); switch looks at a tick with
460
+ `og.load.look` and `og.render.applyLook` (a flashback, a photo mode, an
461
+ area's mood). A look's `MaterialStyle` reaches every lit material that
462
+ leaves the field at its default; a material that must stay as made (a
463
+ photographic hero prop in a toon set, a product shot, a gem) takes
464
+ `styled: false` in `og.render.standardMaterial` and is drawn exactly as
465
+ declared. Physical materials are safe under a toon look: lacquer
466
+ (`clearcoat`), glass and jelly (`transmission`), velvet (`sheen`) keep
467
+ their shading and take only the rim, the outline and the shadow tint, so
468
+ a toon look never switches them off; set `toon: true` on one yourself to
469
+ flatten it too. A look replaces each resource it names whole and leaves the
470
+ others alone, so an area look that names only `Fog` and `Sky` keeps the
471
+ game's grade. A copy of a preset under the preset's own name
472
+ (`assets/looks/<slug>.ogt` with `extends "<slug>"`) overrides it for every
473
+ look of the game that extends that slug. Colours may be written as sRGB
474
+ `"#rrggbb"`; the cook stores them linear. The cook refuses a field the
475
+ resource does not have, a value out of range and an asset not in the
476
+ bundle, naming the line. A look's top-level `antialias` takes `tier`,
477
+ `off`, `msaa4` or `taa` (FXAA is `GameDesc.render`'s, og-game §16), and an
478
+ `Environment` block sets an environment map (`map "env/dusk.ogt"`) as
479
+ part of the look. A look in a streaming group applies once its group is
480
+ resident. The mechanics (what each resource's fields do, presets, the
481
+ built-in canvases) are og-game §15 and §16.
482
+
483
+ **Expand the art bible.** Fill the catalogue's `artBibleTemplate` with the
484
+ chosen entry and the project's premise, camera and platform, and keep the
485
+ result with the game. It is the brief for every asset request after it:
486
+ asking for one asset never redesigns the style. Then hold the game to the
487
+ style's three `checks` on real frames (`og run --headless --commands` with
488
+ screenshots of the title, play and results), as §10 holds the UI, and
489
+ make a playtest fact of `og.render.look(world)?.name` so a later change
490
+ cannot drop the look unnoticed. The platform's own wizard does the same
491
+ in its style step (`docs/platform-asks.md` in the engine repository): the
492
+ catalogue entry chosen from the premise, the look written, and the
493
+ entry's three checks handed to the critic that reads the frames.
494
+
495
+ | Slug | Family | Style | Support | Skin |
496
+ |---|---|---|---|---|
497
+ | `expressionist-painting` | Paint and line | Expressionist paint | content | `paper` |
498
+ | `bold-comic-illustration` | Paint and line | Bold comic illustration | full | `fantasy` |
499
+ | `watercolor-wash` | Paint and line | Watercolour wash | full | `paper` |
500
+ | `pixel-3d-hybrid` | Pixel and retro | Pixel sprites in 3D sets | full | `fantasy` |
501
+ | `faceted-low-poly` | 3D rendering | Faceted low poly | full | `paper` |
502
+ | `sculpted-clay` | 3D rendering | Sculpted clay | full | `cartoon` |
503
+ | `8bit-pixel` | Pixel and retro | Early console pixels | full | `pixel` |
504
+ | `voxel-worlds` | 3D rendering | Voxel worlds | full | `pixel` |
505
+ | `illuminated-manuscript` | Paint and line | Manuscript page | content | `paper` |
506
+ | `rubber-hose-animation` | Paint and line | Rubber-hose cartoon | content | `cartoon` |
507
+ | `pencil-hatching` | Paint and line | Pencil and hatching | full | `paper` |
508
+ | `gothic-contour` | Paint and line | Gothic ornament | content | `fantasy` |
509
+ | `lush-painted-worlds` | Paint and line | Lush opaque painting | content | `fantasy` |
510
+ | `decorative-storybook` | Paint and line | Storybook ornament | content | `cartoon` |
511
+ | `classical-painting-collage` | Paint and line | Old-master collage | content | `paper` |
512
+ | `detailed-doodle` | Paint and line | Fine-line doodle worlds | content | `paper` |
513
+ | `black-figure-pottery` | Paint and line | Black-figure pottery | content | `fantasy` |
514
+ | `heavy-ink-shadows` | Paint and line | Heavy ink and shadow | full | `fantasy` |
515
+ | `monochrome-pixel` | Pixel and retro | One-ink pixels | full | `pixel` |
516
+ | `16bit-pixel` | Pixel and retro | Late console pixels | full | `pixel` |
517
+ | `detailed-pixel` | Pixel and retro | Dense illustrative pixels | full | `fantasy` |
518
+ | `luminous-pixel` | Pixel and retro | Glowing pixels on dark ground | full | `neon` |
519
+ | `topdown-tile-pixel` | Pixel and retro | Top-down tile worlds | full | `fantasy` |
520
+ | `low-resolution-3d` | 3D rendering | Low-resolution 3D | full | `scifi` |
521
+ | `coarse-pixel` | Pixel and retro | Minimal low-count pixels | full | `pixel` |
522
+ | `anime-cel-3d` | 3D rendering | Anime cel-shaded 3D | full | `neon` |
523
+ | `inked-comic-3d` | 3D rendering | Inked comic 3D | full | `scifi` |
524
+ | `paper-craft` | Shape and material | Paper craft | full | `paper` |
525
+ | `miniature-diorama` | 3D rendering | Miniature diorama | full | `cartoon` |
526
+ | `painterly-3d` | 3D rendering | Painterly 3D | full | `fantasy` |
527
+ | `cinematic-realism` | 3D rendering | Cinematic realism | full | `scifi` |
528
+ | `muted-cinematic-3d` | 3D rendering | Muted theatrical light | full | `scifi` |
529
+ | `layered-silhouette` | Shape and material | Layered silhouettes | full | `neon` |
530
+ | `minimal-geometric` | Shape and material | Minimal geometry | full | `neon` |
531
+ | `neon-vector` | Shape and material | Neon vector lines | full | `neon` |
532
+ | `monochrome-dither-3d` | 3D rendering | Monochrome dithered 3D | full | `pixel` |
533
+ | `luminous-layered-painting` | 3D rendering | Luminous layered depths | full | `fantasy` |
534
+
535
+ Use the catalogue as a vocabulary, never as a list of games to copy: every
536
+ design, character and composition stays original (the platform's rule),
537
+ and the descriptors deliberately name no game or studio.