@origingame/engine 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/web/origin-engine.d.ts +44 -5
  2. package/dist/web/origin-engine.js +279 -19
  3. package/dist/web/origin-engine_bg.wasm +0 -0
  4. package/dist/web/origin-engine_bg.wasm.d.ts +18 -5
  5. package/dist/web-publish/origin-engine-lanes.js +289 -48
  6. package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
  7. package/dist/web-publish/origin-engine.d.ts +44 -5
  8. package/dist/web-publish/origin-engine.js +279 -38
  9. package/dist/web-publish/origin-engine_bg.wasm +0 -0
  10. package/dist/web-publish/origin-engine_bg.wasm.d.ts +18 -5
  11. package/dist/web-publish-2d/origin-engine-lanes.js +289 -48
  12. package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
  13. package/dist/web-publish-2d/origin-engine.d.ts +44 -5
  14. package/dist/web-publish-2d/origin-engine.js +279 -38
  15. package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
  16. package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +18 -5
  17. package/knowledge/README.md +2 -1
  18. package/knowledge/guides/three-to-og.md +393 -0
  19. package/knowledge/knowledge.json +2 -1
  20. package/knowledge/looks/catalogue.json +123 -2
  21. package/knowledge/package.json +3 -2
  22. package/knowledge/scaffold/origin-engine.json +2 -2
  23. package/knowledge/scaffold/template/package.json +1 -1
  24. package/knowledge/scaffold/template/project.ogt +1 -1
  25. package/knowledge/scripts/validate.ts +209 -27
  26. package/knowledge/skills/og-audio/SKILL.md +33 -7
  27. package/knowledge/skills/og-build/SKILL.md +66 -7
  28. package/knowledge/skills/og-core/SKILL.md +24 -8
  29. package/knowledge/skills/og-feel/SKILL.md +11 -3
  30. package/knowledge/skills/og-game/SKILL.md +680 -51
  31. package/knowledge/skills/og-i18n/SKILL.md +7 -7
  32. package/knowledge/skills/og-model/SKILL.md +374 -0
  33. package/knowledge/skills/og-platform-sdk/SKILL.md +7 -1
  34. package/knowledge/skills/og-taste/SKILL.md +7 -0
  35. package/knowledge/skills/og-ui/SKILL.md +39 -8
  36. package/knowledge/wiki/ai.md +20 -20
  37. package/knowledge/wiki/anim.md +537 -63
  38. package/knowledge/wiki/atmosphere.md +13 -13
  39. package/knowledge/wiki/audio.md +160 -20
  40. package/knowledge/wiki/camera.md +13 -13
  41. package/knowledge/wiki/commands.md +11 -11
  42. package/knowledge/wiki/debug.md +13 -13
  43. package/knowledge/wiki/game.md +8 -8
  44. package/knowledge/wiki/i18n.md +7 -7
  45. package/knowledge/wiki/index.md +81 -3
  46. package/knowledge/wiki/input.md +47 -47
  47. package/knowledge/wiki/load.md +83 -57
  48. package/knowledge/wiki/math.md +102 -102
  49. package/knowledge/wiki/nav.md +34 -34
  50. package/knowledge/wiki/net.md +10 -10
  51. package/knowledge/wiki/path.md +14 -14
  52. package/knowledge/wiki/physics.md +111 -100
  53. package/knowledge/wiki/platform.md +44 -44
  54. package/knowledge/wiki/probe.md +86 -86
  55. package/knowledge/wiki/reflect.md +30 -18
  56. package/knowledge/wiki/render.md +1243 -176
  57. package/knowledge/wiki/rng.md +14 -14
  58. package/knowledge/wiki/shapes.md +727 -0
  59. package/knowledge/wiki/stream.md +15 -15
  60. package/knowledge/wiki/system.md +8 -8
  61. package/knowledge/wiki/systems.md +2 -2
  62. package/knowledge/wiki/terrain.md +24 -20
  63. package/knowledge/wiki/tilemap.md +13 -13
  64. package/knowledge/wiki/time.md +4 -4
  65. package/knowledge/wiki/timeline.md +14 -14
  66. package/knowledge/wiki/transform.md +8 -8
  67. package/knowledge/wiki/tween.md +17 -17
  68. package/knowledge/wiki/ui.md +245 -269
  69. package/knowledge/wiki/video.md +5 -5
  70. package/knowledge/wiki/world.md +36 -36
  71. package/package.json +4 -4
  72. package/schema/api.schema.json +2238 -81
  73. package/shell/page.ts +4 -0
  74. package/shell/platform.ts +5 -0
  75. package/shell/player.ts +8 -2
  76. package/shell/transport.ts +6 -0
  77. package/src/generated/abi.ts +40 -1
  78. package/src/geom.ts +260 -0
  79. package/src/index.ts +19 -1
  80. package/src/node/device.ts +5 -2
  81. package/src/node/playtest-run.ts +1 -0
  82. package/src/node/polish.ts +34 -15
  83. package/src/node/run.ts +23 -6
  84. package/src/platform-local.ts +2 -0
  85. package/src/playtest.ts +8 -0
  86. package/src/runtime.ts +88 -6
  87. package/src/ui-kit/settings.ts +9 -0
  88. package/src/web/gl-state.ts +907 -0
  89. package/src/web/shell.ts +1 -1
  90. package/src/web/worker.ts +8 -2
  91. package/types/bindings.json +1222 -453
  92. package/types/index.d.ts +1868 -315
@@ -16,7 +16,7 @@ Two games written from this package alone (`examples/lantern-vale`,
16
16
  `examples/hollowbell`) ship English and Simplified Chinese from string
17
17
  tables: `og polish` found no locale gaps in either, the results template
18
18
  showed its localised Best and New best, a name was typed partly through an
19
- input method, and a skin copied without its CJK `fallback` was refused by
19
+ input method, and a style copied without its CJK `fallback` was refused by
20
20
  the cook with the line named. The Kestrel Stair (`examples/kestrel-stair`)
21
21
  ships English and Japanese: its Noto Sans JP subset cut by the ui-kit's
22
22
  `tools/subset-font.py` needed no other step, and its locales playtest
@@ -125,12 +125,12 @@ string "results.time" "タイム {time}"
125
125
  take a `label` (else the reader hears the id) and `IconDesc` and
126
126
  `ImageDesc` a `label` (else an icon is its atlas name, `star`; `""` is
127
127
  decoration): og-ui §4.
128
- - Latin and Cyrillic: the skin's font. Every other script: a subset of a
128
+ - Latin and Cyrillic: the style's font. Every other script: a subset of a
129
129
  Noto face for that script holding exactly the characters your tables
130
130
  use, named first in that table's `fonts` line.
131
131
  - What the engine ships: `fonts/NotoSansSC-Regular-subset.ttf`, in every
132
132
  project (Simplified Chinese: GB2312's 6,763 hanzi, kana and Latin-1,
133
- 2.5 MB), the source for a Chinese subset and the skins' CJK `fallback`.
133
+ 2.5 MB), the source for a Chinese subset and the styles' CJK `fallback`.
134
134
  The ui-kit's `NotoSansJP-subset.otf`, `NotoSansKR-subset.otf` and its
135
135
  Arabic, Hebrew and Devanagari subsets hold only the ui-kit's own strings
136
136
  (the Japanese one 246 characters, 74 of them kanji): they are no source
@@ -152,13 +152,13 @@ string "results.time" "タイム {time}"
152
152
  ```
153
153
 
154
154
  `--cjk-only` leaves Latin out, so a label mixing scripts takes its Latin
155
- from the skin's face run by run; with Latin kept, the whole label is
155
+ from the style's face run by run; with Latin kept, the whole label is
156
156
  drawn in the subset (og-ui §7 has the rule).
157
157
  - Never take Japanese from the Chinese face: it lacks the Japanese forms of
158
158
  common kanji (鐘 鈴 険 続 設 語 楽 効 are not in it) and draws the kanji
159
159
  both scripts share in their Chinese shapes. The fallback chain below is a
160
160
  safety net for a character a subset missed, not a font for a language:
161
- a Japanese string that reaches the skin's Chinese `fallback` is drawn in
161
+ a Japanese string that reaches the style's Chinese `fallback` is drawn in
162
162
  Chinese forms, and one no font has is a box with a warning on the first
163
163
  frame that draws it.
164
164
  - Each font's import settings (`<font>.ogt`) list its characters (`chars`)
@@ -167,12 +167,12 @@ string "results.time" "タイム {time}"
167
167
  outlines, which og-ui's shaper needs.
168
168
  - og-ui looks a glyph up in the active locale's fonts first, then every
169
169
  other loaded table's, then `UiDesc.fallbackFonts` (fonts for scripts no
170
- table names: a Hebrew or Devanagari face), then the skin's. A box (tofu)
170
+ table names: a Hebrew or Devanagari face), then the style's. A box (tofu)
171
171
  in a frame means a font lacks the character: add it to the subset and
172
172
  to `chars`. The engine's log says so on the tick a character is first
173
173
  drawn, once per character, naming it, the font's asset and what its atlas
174
174
  holds (`WARN tick 2603 a frame drew "−" U+2212, which font
175
- skins/neon/ChakraPetch-BoldItalic.ttf lacks (its atlas holds charset
175
+ styles/neon/ChakraPetch-BoldItalic.ttf lacks (its atlas holds charset
176
176
  latin punct)`): the packs cook `latin punct`, which has the hyphen-minus
177
177
  `-` but not the minus sign U+2212 nor the arrows U+2190 to U+2193. One
178
178
  label is drawn in one font, so the font that covers a
@@ -0,0 +1,374 @@
1
+ ---
2
+ name: og-model
3
+ description: Building a 3D prop or stylised character for Origin Engine as code from one reference image, the way img2threejs does it — an intake that inventories every identity-defining detail, a spec, eight build passes (blockout, structure, form, material, surface detail, lighting, interaction, optimisation) each closed by a gate, the og-geom generators (latheGeometry, extrudeGeometry with og.Shape and og.Path2, sweepGeometry, csgGeometry, mergeGeometries, shapeGeometry, the polyhedra) and their recipes, the triangle and draw budgets, the `type model` asset the cook builds a finished prop into, and the deterministic review — the view and turntable commands and og compare's silhouette IoU, per-region ΔE00 and one reference-beside-render sheet. Use when a game needs a model that is not a downloaded glTF, when asked to rebuild an object from a picture, or when a procedural model looks wrong.
4
+ ---
5
+
6
+ A model on Origin Engine can be TypeScript: a function that builds an
7
+ object from generators, every part an entity under one root. That code is
8
+ the source, diffable and reviewable, and it is built from a reference image
9
+ the way img2threejs builds a three.js factory: a spec first, then eight
10
+ passes from blockout to optimisation, each closed by a gate read off one
11
+ sheet. The engine does the mechanical half of every gate (fixed cameras,
12
+ the silhouette and colour numbers, the sheet), so the one judgment left is
13
+ yours: does this sheet pass.
14
+
15
+ The generators are og-game §18 (their fields are on its reference pages);
16
+ this skill is how to build a model with them and how to prove it matches.
17
+
18
+ What is reachable from one image: hard-surface props (crates, lanterns,
19
+ tools, furniture, vehicles, weapons, machines) and stylised characters
20
+ read from a front or three-quarter view. What is not: photoreal likeness,
21
+ the exact dimensions of something seen at an angle, and the sides the
22
+ image does not show. Say so in the spec instead of inventing them, and ask
23
+ for another view when a hidden side matters.
24
+
25
+ ## What works
26
+
27
+ This skill's lantern (§5) was built and reviewed through this loop on the
28
+ native host: the first pass's side and orbit views showed the handle
29
+ floating 0.18 m above the cap and the crest standing off the cap's slope,
30
+ neither visible from the front; the second pass moved the handle's ends
31
+ onto the cap and laid the crest on its 47° slope, and every view agreed.
32
+ `og compare` of a lamp's front view against itself gave IoU 1 and ΔE00 0,
33
+ against its back view (the mirror) IoU 0.24, and against the turntable's
34
+ first frame (the front from 20° up, in perspective) with `--align` IoU
35
+ 0.74, the foot's ellipse and the arm's foreshortening outlined in red and
36
+ blue on the sheet.
37
+
38
+ ## 1. The loop
39
+
40
+ ```text
41
+ reference ─▶ intake (§2) ─▶ spec ─▶ pass n (§4) ─▶ views (§3) ─▶ og compare ─▶ sheet
42
+ ▲ │
43
+ └──── refine-code / refine-spec ◀────────┤
44
+ continue ──▶ pass n + 1
45
+ ```
46
+
47
+ 1. **Intake** (§2): judge the reference, write the detail inventory and
48
+ the region mask, list what the image hides.
49
+ 2. **Spec**: one file in the project, `review/<model>/spec.md`, outside
50
+ `assets/` (nothing cooks it): the parts and their hierarchy, each
51
+ part's generator and material, the pivots, the critical features (at
52
+ most five), the budget (§6) and the inventory's table. The spec is the
53
+ authority; the code follows it.
54
+ 3. **Build one pass** (§4), and only that pass: what a later pass owns
55
+ stays rough.
56
+ 4. **Render the views** (§3): the reference's view, the other three
57
+ orthographic views and a turntable, at the reference's size.
58
+ 5. **Compare**: `og compare` against the reference, with its mask from the
59
+ material pass on. Read the numbers against the pass's gate, then read
60
+ the sheet.
61
+ 6. **Decide**, and write the decision with its numbers in the spec:
62
+ - `continue`: the gate passes and no critical feature is wrong;
63
+ - `refine-code`: the spec is sound and the build misses it;
64
+ - `refine-spec`: the spec is wrong or too shallow (a part missing, a
65
+ form classed as the wrong generator); fix the spec, then the code;
66
+ - `request-input`: the image cannot answer the question (a hidden
67
+ side, an ambiguous material);
68
+ - `stop`: the target cannot be reached from this image; say why.
69
+
70
+ At most three corrections per pass and six over the model: past them,
71
+ stop and report what still does not match. A number that passes does
72
+ not overrule a critical feature that is wrong on the sheet, and a
73
+ feature that looks right does not overrule a number that fails.
74
+
75
+ ## 2. Intake: the reference, the detail inventory, the mask
76
+
77
+ **The reference.** One object, whole in the frame and not cropped, on a
78
+ plain backdrop (the silhouette is keyed from the border), lit evenly
79
+ enough that the materials read, from a known view (front, side or three
80
+ quarters). A cut-out with transparency is best: its alpha is the
81
+ silhouette. Refuse, or ask for another, when the object is cut off, mostly
82
+ occluded, motion-blurred or shot with a wide lens close up (its proportions
83
+ are not the object's).
84
+
85
+ **The detail inventory.** Split the reference into a 3 × 3 grid and list,
86
+ zone by zone, every detail that makes the object this object: the
87
+ silhouette's breaks, joints and seams, repeated systems (bolts, teeth,
88
+ ribs, slats, rivets), relief (bevels, grooves, panel lines, chamfers),
89
+ material regions and markings. Name each in 3D terms and give it a home:
90
+ a part and the generator that makes it, or a material region; never prose
91
+ alone. Mark the critical ones (at most five, the features a person would
92
+ name to recognise it) and the pass that builds each:
93
+
94
+ | Zone | Detail | In 3D | Part, generator | Pass |
95
+ |---|---|---|---|---|
96
+ | top centre | arched handle, thinner at its ends | swept circle, scale 0.6 → 1 → 0.6 | `handle`, sweepGeometry | 3 |
97
+ | middle | four glazed windows | box minus two crossed boxes | `frame`, csgGeometry | 2 |
98
+ | bottom | four rivets round the foot | one sphere, four placements | `rivets`, mergeGeometries | 5 |
99
+
100
+ Write down what one view cannot show (the back, the underside, the depth
101
+ of a part seen face on) and the confidence of each region, and decide each
102
+ from the object's kind, not from nothing: a lantern's back is its front.
103
+
104
+ **The region mask.** A PNG the reference's size, drawn over it: one flat
105
+ colour per material region (brass, iron, glass), black where nothing is
106
+ judged, no antialiasing (a blended edge is a region of its own; at most 64
107
+ colours). `og compare` names each region by its hex, so keep the legend in
108
+ the spec: `#c08030 brass`, `#202024 iron`.
109
+
110
+ ## 3. The review frames: views, turntable, compare
111
+
112
+ **Views.** The command channel frames one entity's drawn bounds (its
113
+ og.Parent children with it) from a fixed direction, in place of og.Camera,
114
+ which it never writes:
115
+
116
+ | Step | Camera |
117
+ |---|---|
118
+ | `view:<entity>:front` | orthographic from +Z looking down -Z, +X right: the face of a model that faces +Z (the glTF and three.js convention) |
119
+ | `view:<entity>:back` | orthographic from -Z, +X left |
120
+ | `view:<entity>:side` | orthographic from +X, +Z left: a model facing +Z shows its left side |
121
+ | `view:<entity>:top` | orthographic from +Y, the front at the bottom |
122
+ | `view:<entity>:orbit:<degrees>` | a 30° perspective from 20° above the horizon, the azimuth from the front towards +X |
123
+ | `view:off` | og.Camera again |
124
+ | `turntable:<entity>:<n>[:<prefix>]` | n orbit frames 360/n apart (at most 72), all of the next tick, written `<prefix>-00.png` on; the view before comes back |
125
+
126
+ The view holds until `view:off` and frames what the entity draws each
127
+ frame, with a tenth of the frame free on each side. Build the model
128
+ facing +Z with y up, its feet on y = 0, so front means front. Its
129
+ og.Mesh and og.Model parts are framed; a skinned model's posed parts and
130
+ sprites are not.
131
+
132
+ Render at the reference's size, so the frames compare without
133
+ resampling, and give og.Camera a backdrop the object does not share
134
+ (Camera.background), so the silhouette keys cleanly:
135
+
136
+ ```sh
137
+ og run . --headless --size 512x512 --commands "0.1:view:lantern:front;0.1:screenshot:review/front.png;0.1:view:lantern:side;0.1:screenshot:review/side.png;0.1:view:lantern:top;0.1:screenshot:review/top.png;0.1:view:lantern:back;0.1:screenshot:review/back.png;0.2:turntable:lantern:8:review/spin;0.3:quit"
138
+ ```
139
+
140
+ The frames land under `og-out/` at the paths named. Every view of one
141
+ tick shows the same world, so a script can take all of them at once.
142
+
143
+ **Compare.**
144
+
145
+ ```sh
146
+ og compare refs/lantern-front.png og-out/review/front.png --mask refs/lantern-front.mask.png
147
+ ```
148
+
149
+ prints, for a two-region mask:
150
+
151
+ ```text
152
+ silhouette IoU 0.9312 (512x512: reference 61210 px, render 63004 px, both 58450 px)
153
+ ΔE00 over the overlap: mean 6.84, p95 18.20
154
+ region pixels both IoU ΔE00 mean p95
155
+ #202024 40122 37610 0.9133 4.10 11.52
156
+ #c08030 12090 11211 0.8890 9.75 21.04
157
+ ```
158
+
159
+ - **Silhouette IoU** is the object's pixels in both over the pixels in
160
+ either. The silhouette is alpha ≥ 128 when an image has transparency,
161
+ else every pixel more than `--key` ΔE00 (8) from the backdrop (the median
162
+ of the border); raise `--key` for a noisy photo backdrop.
163
+ - **ΔE00** is the CIEDE2000 difference of the colours over the pixels both
164
+ cover, per mask region (each with its own IoU) or over the whole overlap
165
+ without a mask: mean and 95th percentile. Under 2 is the same colour to
166
+ the eye, 2 to 5 close, 5 to 10 a visible difference, above 10 another
167
+ colour. The render's light is part of its colour: compare under light
168
+ like the reference's, and read ΔE as a material number only once the
169
+ lighting pass is settled.
170
+ - **The sheet** is one PNG beside the render (`front.sheet.png`, or
171
+ `--sheet <path>`): the reference, the render as compared, and the two
172
+ silhouettes overlaid (grey both, red the reference only, so missing;
173
+ blue the render only, so extra), labelled with the ΔE00 and the IoU.
174
+ Read it whole before deciding.
175
+ - The render is compared in the reference's frame: resampled when the two
176
+ have the same shape, refused when not (render at `--size` of the
177
+ reference's). `--align` scales the render's silhouette box to the
178
+ reference's and centres it, for a reference whose framing is not the
179
+ view's (a photo with its own margins); the framing then no longer counts
180
+ against the IoU, and proportions still do. `--json` prints the numbers
181
+ as one object.
182
+
183
+ **What the numbers cannot see.** The IoU reads the outline only: a model
184
+ with a feature missing inside its outline (a window, an eye, a panel
185
+ line) scores the same. A straight cone in roughly the right place passes
186
+ a curved horn's outline. One view says nothing of the depth. So every
187
+ pass reads the side, top and turntable frames too (a part that floats, a
188
+ hole through the body, a hat at the hip show there and only there), the
189
+ mask's regions measure inside the outline, and the critical features are
190
+ judged one by one on the sheet.
191
+
192
+ ## 4. The eight passes and their gates
193
+
194
+ Build in order; each pass touches only its own layer. The gates are
195
+ defaults for a prop in a front reference with `--align`; raise them for a
196
+ hero prop, and write the gate the spec uses.
197
+
198
+ | # | Pass | Builds | Gate (numbers, then the sheet) |
199
+ |---|---|---|---|
200
+ | 1 | Blockout | the primary volumes as boxes, cylinders and lathes at their size and place; the root at the object's base, facing +Z | IoU ≥ 0.80; the bounds' width, height and depth in the reference's proportions within 5 %; nothing floats in the side and top views |
201
+ | 2 | Structure | every part of the inventory as its own entity under the root (og.Parent), its pivot where it turns or opens; repeated systems as one part placed many times | IoU ≥ 0.88; every part the spec names answers `get:<part>`; each critical feature is present on the sheet |
202
+ | 3 | Form | the outlines refined: lathe profiles, sweeps with their taper and twist, extrusions with their curves and bevels, the cuts | IoU ≥ 0.92 (≥ 0.95 for a hero prop); the turntable shows no hole, gap or floating part; a curve claimed in the spec reads as a curve |
203
+ | 4 | Material | a material per mask region: colour, roughness, metalness, emissive | per region ΔE00 mean ≤ 10 (≤ 6 on a critical region), p95 ≤ 20; IoU not below pass 3's |
204
+ | 5 | Surface detail | the inventory's small details: grooves and holes (csgGeometry), rivets and bolts (mergeGeometries), plates and badges (extrudeGeometry, shapeGeometry), bevels | every inventory row ticked on the sheet; no region's ΔE00 worse than pass 4 by more than 1 |
205
+ | 6 | Lighting | the model under the game's light and look (og-taste picks it): the frame the player will see | no critical feature lost in shadow or blown out; ΔE00 recorded, not gated (the game's light is not the photo's) |
206
+ | 7 | Interaction | what moves: hinges and lids turn about their pivots, named parts answer a pick, colliders where the game needs them | a scripted run turns each moving part through its range with no part leaving its joint; `og.render.raycast` at a part answers that part's entity |
207
+ | 8 | Optimisation | the budget (§6): static parts sharing a material merged, segment counts lowered where the silhouette does not need them; a static prop written as a `type model` asset (§5) | within the budget; IoU not below pass 3's by more than 0.01; the turntable unchanged to the eye |
208
+
209
+ A gate that fails is a decision (§1), not a reason to move on. When the
210
+ number and the sheet disagree, the sheet's specific defect wins: write it
211
+ in the spec and fix that.
212
+
213
+ ## 5. The generators: recipes
214
+
215
+ Pick the generator by the form, never by habit: a continuous organic form
216
+ is a lathe or a sweep, not a stack of boxes.
217
+
218
+ | Form | Generator | Notes |
219
+ |---|---|---|
220
+ | turned: vases, bottles, lamp bases, wheels, columns, domes | `og.render.latheGeometry` | a profile of points, x from the axis, y up; `segments` 24 to 48 for a hero prop, 12 to 16 for a prop |
221
+ | a flat profile with depth: gears, signs, keys, brackets, badges | `og.render.extrudeGeometry` with an og.Shape (holes in `holes`, as og.Path2) | the bevel is on by default (three.js's 0.2 thick): `bevelEnabled: false` for a crisp plate, or set `bevelThickness` and `bevelSize` to the edge you see |
222
+ | tapered and curved: horns, tusks, handles, tails, blades, cables | `og.render.sweepGeometry` along an og.path | `scale` and `twist` keys over the path's length, 0 to 1 |
223
+ | holes, notches, windows, pips, grooves | `og.render.csgGeometry` with `subtract` | both operands closed; low-poly operands, cut once at load; chain calls for more cuts |
224
+ | joined solids that must be one surface | `csgGeometry` with `union` | for a single closed mesh; parts that only touch stay separate parts |
225
+ | repeated parts: rivets, teeth, slats, legs, a symmetric half | `og.render.mergeGeometries` | one placement per copy; `mirror: "x"` for the other half |
226
+ | faceted: gems, rocks, low-poly spheres | `og.render.icosahedronGeometry` and the other polyhedra | `detail` 0 faceted, 2 or more round |
227
+ | thin flat plates, decals, a shape seen face on | `og.render.shapeGeometry` | one-sided: give its material `side: "double"` when seen from behind |
228
+ | pipes and rails through points | `og.render.tubeGeometry` | or with `path` to follow an og.path |
229
+
230
+ **Left and right.** A left and right pair is a reflection, never a
231
+ rotation: `mirror: "x"` (or a scale of -1 on x) negates x and nothing
232
+ else, and mergeGeometries turns the mirrored triangles back so they face
233
+ out. A model facing +Z has its own left at +X.
234
+
235
+ **Parts are entities.** Give each part its own entity under the root
236
+ (og.Parent), named as the spec names it: the review frames the root, a
237
+ pick answers the part, a lid turns about its own pivot, and the model can
238
+ be pulled apart to check it. Merge only what never moves and shares a
239
+ material, and only in the optimisation pass.
240
+
241
+ The lantern of the review above, every generator once:
242
+
243
+ ```ts
244
+ import { og } from "@origingame/engine";
245
+ import type { World, Entity, Geometry, Material } from "@origingame/engine";
246
+
247
+ function must<T>(r: { ok: true; value: T } | { ok: false; reason: string }): T {
248
+ if (!r.ok) throw new Error(r.reason);
249
+ return r.value;
250
+ }
251
+
252
+ /** A signal lantern, facing +Z, its foot on y = 0: every part its own entity under the root. */
253
+ export function buildLantern(world: World): Entity {
254
+ const iron = must(og.render.standardMaterial(world, { color: { r: 0.12, g: 0.12, b: 0.13 }, roughness: 0.6, metalness: 0.8 }));
255
+ const brass = must(og.render.standardMaterial(world, { color: { r: 0.78, g: 0.55, b: 0.22 }, roughness: 0.35, metalness: 1 }));
256
+ const glass = must(og.render.standardMaterial(world, { color: { r: 1, g: 0.75, b: 0.35 }, emissive: { r: 1, g: 0.6, b: 0.2 }, emissiveIntensity: 2 }));
257
+ const root = must(world.spawn({ name: "lantern" }));
258
+ const part = (name: string, geometry: Geometry, material: Material, position = { x: 0, y: 0, z: 0 }) => {
259
+ const e = must(world.spawn({ name, position, parent: root }));
260
+ world.insert(e, og.Mesh, { geometry, material });
261
+ return e;
262
+ };
263
+
264
+ // Turned forms: the foot and the cap, profiles turned round +Y.
265
+ const foot = must(og.render.latheGeometry(world, {
266
+ points: [{ x: 0, y: 0 }, { x: 0.42, y: 0 }, { x: 0.42, y: 0.06 }, { x: 0.34, y: 0.12 }, { x: 0.3, y: 0.2 }, { x: 0, y: 0.2 }],
267
+ segments: 32,
268
+ }));
269
+ part("foot", foot, iron);
270
+ const cap = must(og.render.latheGeometry(world, {
271
+ points: [{ x: 0, y: 0.32 }, { x: 0.08, y: 0.3 }, { x: 0.36, y: 0.04 }, { x: 0.34, y: 0 }, { x: 0, y: 0 }],
272
+ segments: 32,
273
+ }));
274
+ part("cap", cap, iron, { x: 0, y: 0.9, z: 0 });
275
+
276
+ // Cuts: the housing, a box with a window bored through each pair of sides.
277
+ const body = must(og.render.boxGeometry(world, { width: 0.5, height: 0.7, depth: 0.5 }));
278
+ const pane = must(og.render.boxGeometry(world, { width: 0.34, height: 0.5, depth: 0.7 }));
279
+ const quarter = og.transform.fromAxisAngle({ x: 0, y: 1, z: 0 }, Math.PI / 2);
280
+ const front = must(og.render.csgGeometry(world, { op: "subtract", a: { geometry: body }, b: { geometry: pane } }));
281
+ const frame = must(og.render.csgGeometry(world, { op: "subtract", a: { geometry: front }, b: { geometry: pane, quaternion: quarter } }));
282
+ part("frame", frame, iron, { x: 0, y: 0.55, z: 0 });
283
+ const flame = must(og.render.icosahedronGeometry(world, { radius: 0.12, detail: 2 }));
284
+ part("flame", flame, glass, { x: 0, y: 0.5, z: 0 });
285
+
286
+ // A tapered sweep: the handle, an arc whose ends sit on the cap, thinner at the ends.
287
+ const arc = must(og.path.create(world, {
288
+ kind: "catmullRom",
289
+ points: [{ x: -0.26, y: 1.0, z: 0 }, { x: -0.18, y: 1.4, z: 0 }, { x: 0.18, y: 1.4, z: 0 }, { x: 0.26, y: 1.0, z: 0 }],
290
+ }));
291
+ const ring = new og.Shape().absarc(0, 0, 0.025, 0, Math.PI * 2, false);
292
+ const handle = must(og.render.sweepGeometry(world, {
293
+ section: ring, path: arc, steps: 48,
294
+ scale: [{ t: 0, value: 0.6 }, { t: 0.5, value: 1 }, { t: 1, value: 0.6 }],
295
+ }));
296
+ part("handle", handle, brass);
297
+
298
+ // A flat profile pushed to depth: the crest, with a hole and a crisp
299
+ // edge, laid on the cap's slope (47° back) so it sits on the cap.
300
+ const crest = new og.Shape().moveTo(-0.12, 0).lineTo(0.12, 0).lineTo(0, 0.2).closePath();
301
+ crest.holes.push(new og.Path2().absarc(0, 0.07, 0.035, 0, Math.PI * 2, true));
302
+ const plate = must(og.render.extrudeGeometry(world, { shapes: crest, depth: 0.02, bevelEnabled: false }));
303
+ const badge = part("crest", plate, brass, { x: 0, y: 0.96, z: 0.33 });
304
+ world.set(badge, og.Transform, { quaternion: og.transform.fromAxisAngle({ x: 1, y: 0, z: 0 }, -0.82) });
305
+
306
+ // A repeated part: four rivets round the foot, one draw.
307
+ const rivet = must(og.render.sphereGeometry(world, { radius: 0.025 }));
308
+ const rivets = must(og.render.mergeGeometries(world, {
309
+ parts: [0, 1, 2, 3].map((k) => {
310
+ const a = (k * Math.PI) / 2 + Math.PI / 4;
311
+ return { geometry: rivet, position: { x: 0.36 * Math.sin(a), y: 0.09, z: 0.36 * Math.cos(a) } };
312
+ }),
313
+ }));
314
+ part("rivets", rivets, brass);
315
+ return root;
316
+ }
317
+ ```
318
+
319
+ Keep the numbers of the spec in the code (the profile's points, the
320
+ heights, the taper's keys) and nowhere else, so a refine-code is an edit
321
+ of a number the spec names.
322
+
323
+ **The same model as an asset: `type model`.** A prop whose parts depend on
324
+ nothing the game computes belongs in an asset once its passes are
325
+ closed: an `.ogt` of `type model` under `assets/` (og-game §18 has the
326
+ grammar and a whole example) names each part's generator by its
327
+ `og.render` name with the same desc fields, places it as
328
+ `mergeGeometries` places a part (`at { }` blocks for the copies of a
329
+ repeated part), gives it a `material` and hangs it from a named `pivot`,
330
+ the node a lid or a wheel turns about. `og cook` builds it into the
331
+ model blob a glTF cooks to: meshopt, levels of detail with their measured
332
+ errors, the raycast BVH, the nodes, repeated parts drawn instanced. It
333
+ refuses the model past project.ogt's triangle and vertex budgets (write
334
+ §6's row for the model there, so the budget is the cook's, not a
335
+ promise), and `og cook --report` lists its triangles, parts and draws.
336
+ Load it with `og.load.gltf(world, "models/lantern.ogt")` and
337
+ `world.instantiate`, or name it on a scene's `model` line; `og dev`
338
+ re-cooks an edit under the same handle. Keep the TypeScript factory while
339
+ a pass is open (a refine-code is an edit and a rerun), write the asset in
340
+ the optimisation pass, and review the asset's views against the factory's
341
+ with `og compare` before deleting the factory: a region that moved is a
342
+ part the asset places differently.
343
+
344
+ ## 6. Budgets
345
+
346
+ | Use | Triangles | Materials (draws) | Lathe and sweep segments |
347
+ |---|---|---|---|
348
+ | hero prop, seen close and turned in the hand | ≤ 20,000 | ≤ 6 | 32 to 64 |
349
+ | prop in the play space | ≤ 5,000 | ≤ 3 | 16 to 24 |
350
+ | background and repeated set dressing | ≤ 1,000 | 1 | 8 to 12 |
351
+ | stylised character | ≤ 15,000 | ≤ 4 | 24 to 32 |
352
+
353
+ - Count with `og.render.geometryInfo(world, g).triangles` over the parts,
354
+ and in a frame with `dump_render` (the main pass's triangles).
355
+ - A draw is a mesh and material pair: parts that never move and share a
356
+ material merge into one with mergeGeometries in the optimisation pass,
357
+ not before.
358
+ - csgGeometry's cost grows with the product of its operands' triangles
359
+ where their boxes overlap: cut low-poly operands once at load, never in
360
+ a system.
361
+ - `curveSegments` 8 to 12 is enough for an og.Shape's arcs at prop size; a
362
+ silhouette that shows facets in the front view wants more, an inner hole
363
+ seldom does.
364
+ - A model used many times in a scene is built once and its geometries
365
+ shared: call the factory's generators once and give each copy its own
366
+ entities with the same geometry and material handles.
367
+
368
+ ## 7. Reporting
369
+
370
+ Say what each pass changed with its numbers (the IoU and ΔE00 before and
371
+ after, the parts added, the triangles), what still does not match, and the
372
+ sheet it was judged on. Never say done when the sheet says improved. A
373
+ model approximated, stylised or guessed at its hidden sides is reported as
374
+ such, region by region.
@@ -88,6 +88,12 @@ if (answer.ok && typeof answer.data.value === "number") run.best[i] = Math.max(r
88
88
  | AI-generated content shown | `aiMark(world, { kind, label })` | disclosure, not permission |
89
89
  | Play with others in one room | `roomCreate(world, { name })`, `roomJoin(world, room)`, `roomLeave`, `roomSend(world, data, to?)`, `roomPeers`, `roomInfo`; the answers as `og.RoomJoined`, `og.RoomPeerJoined`, `og.RoomPeerLeft`, `og.RoomMessage` (read one with `roomMessage`) | natively `og relay` with `og run --room ws://127.0.0.1:P/<room>`; on the web the Player's rooms, when its hello lists `room.join` (`roomInfo(world).host` is `none` otherwise and joins are refused). A message is this peer's host input only: for a game both peers simulate, use og.net's lockstep (`og.Lockstep`, `og.PlayerInput`, og-game §20) and send nothing yourself |
90
90
 
91
+ A payload keeps the number it was given: an `f32` field's value is single
92
+ precision promoted to a JSON number (`f32(2.7)` sends `2.700000047683716`),
93
+ and the store neither rounds nor widens it. Compare with a tolerance in
94
+ a playtest, round only for display, or keep a value that must compare
95
+ exactly in an `f64` (og-build §1, Values in the JSON).
96
+
91
97
  `og.platform.info(world)` says what answers here (`player`, `local` or
92
98
  `none`) and the methods it answers (`capabilities`): check it rather than
93
99
  trying.
@@ -114,7 +120,7 @@ trying.
114
120
  only starts after the tap, still shows its defaults behind the gate.
115
121
  - The frame behind the gate is a still of the opening title a few ticks
116
122
  in, held until the tap, so anything on it that moves into place over
117
- its first second stands part-way there: the skin's panel entrance and
123
+ its first second stands part-way there: the style's panel entrance and
118
124
  its controls' stagger (about half a second), a transition, a fade the
119
125
  game plays itself. Build the screen the game opens on settled while it
120
126
  is that screen, and let every later screen enter as usual:
@@ -342,6 +342,13 @@ the look lives in authored art (painted backdrops, frame-by-frame
342
342
  animation, hand-placed pixels); the engine frames it with the look, but
343
343
  the assets carry it, so budget for them.
344
344
 
345
+ **Read `look.profiles` before matching.** It names the project profiles a
346
+ style draws for: `["2d"]` for flipbooks, hand-placed pixels and paper
347
+ cut-outs, `["3d"]` for lit meshes, toon materials and voxels, both for a
348
+ post treatment that frames either. `create --premise` matches only among the
349
+ styles of the game's profile (`--profile`, else the premise's, else 3D);
350
+ `--look` names any style and wins.
351
+
345
352
  **Write the look.** Start from the style's preset and change only what the
346
353
  game needs; your file wins field by field. A style whose `look.presetReady`
347
354
  is false has no preset in this engine yet: leave out `extends` and set the
@@ -4,8 +4,9 @@ description: Game screens and HUDs on Origin Engine with og.ui — which screen
4
4
  ---
5
5
 
6
6
  `og.ui` is the engine's immediate-mode UI: a system describes the tree every
7
- tick, the engine lays it out, draws it in the platform's design tokens and
8
- answers the probe (`dump_ui`, `touch_move_to`, `move_to_text`). The API
7
+ tick, the engine lays it out, draws it in the game's UI style (a look's
8
+ `ui` line; the `prototype` placeholder without one, never the platform's
9
+ tokens) and answers the probe (`dump_ui`, `touch_move_to`, `move_to_text`). The API
9
10
  reference is `reference/ui.md`; this skill is how to use it well.
10
11
 
11
12
  ## What works
@@ -56,7 +57,7 @@ and every style pack's screens on a phone `kit-style-<pack>-phone.png`.
56
57
  |---|---|---|
57
58
  | A title screen | `og.ui.mainMenu(id, { title, subtitle, items, footer, instant })` (`instant` while it is the screen the game opens on: §6) | `selected`: the item id |
58
59
  | Pause | `og.ui.pauseMenu(id, { open, items })` (Resume, Settings, Quit by default) | `selected`; Back answers `resume` |
59
- | Settings | `og.ui.settingsScreen(id, { open, settings, languages, onChange, onSave })` | the new `GameSettings`, `changed`, `closed` |
60
+ | Settings | `og.ui.settingsScreen(id, { open, settings, languages, world, onChange, onSave })` (with `world`, a new graphics preset is applied: `og.render.applyQuality`) | the new `GameSettings`, `changed`, `closed` |
60
61
  | A platformer HUD | `og.ui.platformerHud(id, { lives, maxLives, coins, score, level, timeLeft, touch })` (what it is not given it does not draw: `maxLives: 0` no hearts, no `coins` or `score` no box for them; the words `pauseLabel`, `jumpLabel` from the string table, og-i18n §2) | `paused`, `moveX`, `action: "jump"` |
61
62
  | A shooter HUD | `og.ui.shooterHud(id, { health, armor, ammo, reserve, abilities, map, touch })` (the words `killsLabel`, `fireLabel`, `pauseLabel`) | `paused`, `action` (ability id, `fire`), stick |
62
63
  | A puzzle HUD | `og.ui.puzzleHud(id, { level, score, target, stars, moves, timeLeft })` (the words `movesLabel`, `targetLabel` with `{target}`, `pauseLabel`) | `paused` |
@@ -104,6 +105,30 @@ layers (`anchor` for HUD corners, `modal` for dialogs,
104
105
  playtest can `touch_move_to` it (`Shop/Panel/Detail/Buy` resolves as
105
106
  `Detail/Buy`).
106
107
 
108
+ **Text over a world point** (a name tag over a critter, a marker over a
109
+ door): project the point each tick with the og.Camera resource's
110
+ `worldToScreen` (null behind the camera: draw nothing) and draw screen
111
+ text there, not world text. World text (`og.render.text` without `screen`) faces +z, rides
112
+ the tone curve and is blurred by depth of field at the frame's edge;
113
+ screen text is never tone mapped, blurred or depth tested, so the tag
114
+ stays crisp and in its colour, and at a `layer` up to `UI_LAYER` (1000) it
115
+ draws under every UI element (the HUD and the dialogs cover it):
116
+
117
+ ```ts
118
+ const Tags = defineSystem("Tags", { reads: [Game, og.Camera] }, (_q, _t, world) => {
119
+ const cam = must(world.resource(og.Camera));
120
+ for (const c of critters(world)) {
121
+ const at = cam.worldToScreen({ x: c.x, y: c.y + 1.25, z: c.z }); // CSS pixels, y down
122
+ if (at === null) continue;
123
+ og.render.text(world, { font, text: c.name, position: { x: at.x, y: at.y, z: 0 }, size: 18, align: "center", outline: 0.7, screen: true, layer: 10 });
124
+ }
125
+ });
126
+ ```
127
+
128
+ Keep the font in a `Font()` field. Two tags within a tag's width of each
129
+ other overlap into one label: lift the later one by its height, or hide
130
+ the farther one's.
131
+
107
132
  Mixed styles in one paragraph are `richText(id, markup)`: `[b]`,
108
133
  `[color=danger]` (a token, or `#rrggbb`), `[size=lg]`, `[font=mono]`,
109
134
  `[icon=coin]`, `[link=shop]…[/link]` and the per-glyph `[wave]`, `[shake]`,
@@ -118,13 +143,15 @@ wire. On a Mac (`Viewport.macKeys`: the web shell sets it from the
118
143
  browser's platform, a native host on macOS) Cmd+A, C, X, Z are the
119
144
  shortcuts, Cmd+←/→ go to the line's ends, Option+←/→ (and
120
145
  Option+Backspace or Delete) move by words, and Ctrl takes the emacs keys
121
- (A/E the line's ends, F/B a character, N/P a line, D/H delete one, K to
122
- the paragraph's end, U to its start, W the word before); elsewhere Ctrl
146
+ (A/E the line's ends, F/B a character, N/P a line, D/H delete one, K
147
+ kills to the paragraph's end, U to its start, W the word before, Y yanks
148
+ what they killed, T transposes); elsewhere Ctrl
123
149
  takes the shortcuts and moves by words. Words are Unicode's (UAX #29):
124
150
  `can't` is one, each Chinese character is one, Thai moves a cluster at a
125
151
  time. The caret and Backspace step over a whole grapheme cluster (`é`
126
- typed as e and an accent, an emoji with its skin tone), and ←/→ move as
127
- they point: in Hebrew or Arabic ← goes on in the text. A double click selects a word and a triple
152
+ typed as e and an accent, an emoji with its skin tone), and ←/→ move on
153
+ screen: in Hebrew or Arabic ← goes on in the text, and across the edge of
154
+ a Hebrew word in a Latin line the caret goes where the arrow points. A double click selects a word and a triple
128
155
  click the line, Shift+Z or Y redoes (typing undoes a word at a time, an
129
156
  input method's commit at a time), and a drag past an area's edge scrolls
130
157
  it. A text the game sets starts the field's undo history again. Script a
@@ -256,7 +283,11 @@ phone), down past it. Every HUD template and any HUD built from
256
283
  `og.ui.anchor` groups gets this; a corner drawn another way (screen
257
284
  sprites, a row placed by hand) reads `covered` itself, so put a game's own
258
285
  HUD in anchored groups. Preview it
259
- with `og.ui.previewViewport(world, { ..., covered: { x: 12, y: 12, width: 40, height: 40 } })`.
286
+ with `og.ui.previewViewport(world, { ..., covered: { x: 12, y: 12, width: 40, height: 40 } })`,
287
+ or give a native run the page's cover from outside the game with the
288
+ command `cover:12,12,40,40` (`og run --setup cover:12,12,40,40`; a
289
+ playtest's `setup: "cover:12,12,40,40"`, og-build §6), `cover:none`
290
+ clearing it.
260
291
  `og.ui.frameViewport().breakpoint` is `phonePortrait`, `phoneLandscape`,
261
292
  `tablet` or `desktop`; templates rearrange themselves by it (tabs become a
262
293
  select, side panels move under, touch controls appear when `touch` is set).