@origingame/engine 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/og.mjs +1 -1
- package/dist/web/origin-engine.d.ts +255 -5
- package/dist/web/origin-engine.js +1584 -125
- package/dist/web/origin-engine_bg.wasm +0 -0
- package/dist/web/origin-engine_bg.wasm.d.ts +80 -3
- package/dist/web-publish/origin-engine-lanes.js +1288 -82
- package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish/origin-engine.d.ts +199 -6
- package/dist/web-publish/origin-engine.js +1285 -79
- package/dist/web-publish/origin-engine_bg.wasm +0 -0
- package/dist/web-publish/origin-engine_bg.wasm.d.ts +65 -4
- package/dist/web-publish-2d/origin-engine-lanes.js +1285 -79
- package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine.d.ts +198 -5
- package/dist/web-publish-2d/origin-engine.js +1282 -76
- package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +64 -3
- package/knowledge/README.md +2 -1
- package/knowledge/guides/three-to-og.md +393 -0
- package/knowledge/knowledge.json +2 -1
- package/knowledge/looks/catalogue.json +40 -40
- package/knowledge/package.json +3 -2
- package/knowledge/scaffold/distribution.json +14 -0
- package/knowledge/scaffold/origin-engine.json +2 -2
- package/knowledge/scaffold/template/README.md +2 -1
- package/knowledge/scaffold/template/assets/audio/sting.ogt +10 -0
- package/knowledge/scaffold/template/assets/timelines/intro.ogt +101 -0
- package/knowledge/scaffold/template/assets/timelines/narration.srt +11 -0
- package/knowledge/scaffold/template/host.d.ts +46 -0
- package/knowledge/scaffold/template/package.json +1 -1
- package/knowledge/scaffold/template/project.ogt +1 -1
- package/knowledge/scaffold/template/src/main.ts +1 -1
- package/knowledge/scaffold/template/tests/frames.ts +134 -0
- package/knowledge/scaffold/template/tests/intro.og.ts +65 -0
- package/knowledge/scaffold/template/tests/smoke.og.ts +30 -36
- package/knowledge/scaffold/template/tsconfig.json +1 -1
- package/knowledge/scripts/mount.ts +2 -1
- package/knowledge/scripts/validate.ts +159 -31
- package/knowledge/skills/og-audio/SKILL.md +2 -2
- package/knowledge/skills/og-build/SKILL.md +29 -7
- package/knowledge/skills/og-core/SKILL.md +31 -5
- package/knowledge/skills/og-feel/SKILL.md +2 -2
- package/knowledge/skills/og-game/SKILL.md +1331 -16
- package/knowledge/skills/og-i18n/SKILL.md +7 -7
- package/knowledge/skills/og-model/SKILL.md +374 -0
- package/knowledge/skills/og-platform-sdk/SKILL.md +2 -1
- package/knowledge/skills/og-taste/SKILL.md +82 -73
- package/knowledge/skills/og-ui/SKILL.md +80 -58
- package/knowledge/wiki/ai.md +20 -20
- package/knowledge/wiki/anim.md +392 -41
- package/knowledge/wiki/atmosphere.md +36 -13
- package/knowledge/wiki/audio.md +18 -18
- package/knowledge/wiki/camera.md +19 -13
- package/knowledge/wiki/commands.md +19 -13
- package/knowledge/wiki/debug.md +414 -0
- package/knowledge/wiki/game.md +8 -8
- package/knowledge/wiki/i18n.md +7 -7
- package/knowledge/wiki/index.md +317 -8
- package/knowledge/wiki/input.md +47 -47
- package/knowledge/wiki/load.md +238 -70
- package/knowledge/wiki/math.md +2607 -0
- package/knowledge/wiki/nav.md +34 -34
- package/knowledge/wiki/net.md +340 -0
- package/knowledge/wiki/path.md +402 -0
- package/knowledge/wiki/physics.md +95 -95
- package/knowledge/wiki/platform.md +463 -29
- package/knowledge/wiki/probe.md +681 -67
- package/knowledge/wiki/reflect.md +29 -17
- package/knowledge/wiki/render.md +4993 -1998
- package/knowledge/wiki/rng.md +14 -14
- package/knowledge/wiki/shapes.md +727 -0
- package/knowledge/wiki/stream.md +15 -15
- package/knowledge/wiki/system.md +8 -8
- package/knowledge/wiki/systems.md +2 -2
- package/knowledge/wiki/terrain.md +24 -20
- package/knowledge/wiki/tilemap.md +13 -13
- package/knowledge/wiki/time.md +4 -4
- package/knowledge/wiki/timeline.md +396 -0
- package/knowledge/wiki/transform.md +8 -8
- package/knowledge/wiki/tween.md +17 -17
- package/knowledge/wiki/ui.md +560 -654
- package/knowledge/wiki/video.md +157 -0
- package/knowledge/wiki/world.md +36 -36
- package/package.json +4 -3
- package/schema/api.schema.json +28151 -19640
- package/shell/page.ts +26 -2
- package/shell/platform.ts +51 -0
- package/shell/player.ts +15 -2
- package/shell/transport.ts +6 -0
- package/src/depth.ts +71 -0
- package/src/e6.ts +32 -1
- package/src/env.d.ts +5 -1
- package/src/generated/abi.ts +226 -4
- package/src/geom.ts +260 -0
- package/src/index.ts +32 -4
- package/src/light.ts +18 -0
- package/src/math.ts +622 -0
- package/src/node/index.ts +17 -0
- package/src/node/playtest-run.ts +115 -18
- package/src/node/polish.ts +13 -3
- package/src/node/render.ts +291 -0
- package/src/node/run.ts +86 -5
- package/src/p2.ts +19 -1
- package/src/platform-local.ts +2 -0
- package/src/playtest.ts +65 -4
- package/src/runtime.ts +100 -9
- package/src/targets.ts +28 -0
- package/src/trace.ts +169 -0
- package/src/ui-kit/huds.ts +34 -34
- package/src/ui-kit/menus.ts +12 -12
- package/src/ui-kit/settings.ts +11 -2
- package/src/ui-kit/store.ts +18 -18
- package/src/ui-kit/story.ts +6 -4
- package/src/ui-kit/util.ts +8 -8
- package/src/web/shell.ts +59 -1
- package/src/web/worker.ts +105 -4
- package/types/bindings.json +9368 -6619
- package/types/index.d.ts +16377 -11520
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
@@ -86,6 +86,7 @@ if (answer.ok && typeof answer.data.value === "number") run.best[i] = Math.max(r
|
|
|
86
86
|
| Analytics | `telemetry(world, "round.over", { score, coins })` | one-way; a name of 1–64 letters, digits, `.` and `_` (the Player files it as `game.custom.<name>`), at most 16 scalar attributes; never personal data |
|
|
87
87
|
| Origin Point | `point(world, { reason, amount })` | the platform decides what it grants (today nothing under a Player: `{ granted: 0, balance: null }` until an approved point policy; show nothing that depends on it) |
|
|
88
88
|
| AI-generated content shown | `aiMark(world, { kind, label })` | disclosure, not permission |
|
|
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 |
|
|
89
90
|
|
|
90
91
|
`og.platform.info(world)` says what answers here (`player`, `local` or
|
|
91
92
|
`none`) and the methods it answers (`capabilities`): check it rather than
|
|
@@ -113,7 +114,7 @@ trying.
|
|
|
113
114
|
only starts after the tap, still shows its defaults behind the gate.
|
|
114
115
|
- The frame behind the gate is a still of the opening title a few ticks
|
|
115
116
|
in, held until the tap, so anything on it that moves into place over
|
|
116
|
-
its first second stands part-way there: the
|
|
117
|
+
its first second stands part-way there: the style's panel entrance and
|
|
117
118
|
its controls' stagger (about half a second), a transition, a fade the
|
|
118
119
|
game plays itself. Build the screen the game opens on settled while it
|
|
119
120
|
is that screen, and let every later screen enter as usual:
|