@origingame/engine 0.0.0-stage → 0.1.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 (158) 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/package.json +18 -0
  42. package/knowledge/scaffold/template/project.ogt +25 -0
  43. package/knowledge/scaffold/template/src/main.ts +246 -0
  44. package/knowledge/scaffold/template/tests/guard.og.ts +67 -0
  45. package/knowledge/scaffold/template/tests/smoke.og.ts +43 -0
  46. package/knowledge/scaffold/template/tsconfig.json +17 -0
  47. package/knowledge/scripts/lib.ts +203 -0
  48. package/knowledge/scripts/mount.ts +210 -0
  49. package/knowledge/scripts/pack.ts +24 -0
  50. package/knowledge/scripts/scaffold.ts +72 -0
  51. package/knowledge/scripts/validate.ts +749 -0
  52. package/knowledge/skills/og-ai/SKILL.md +386 -0
  53. package/knowledge/skills/og-audio/SKILL.md +191 -0
  54. package/knowledge/skills/og-build/SKILL.md +330 -0
  55. package/knowledge/skills/og-core/SKILL.md +285 -0
  56. package/knowledge/skills/og-feel/SKILL.md +385 -0
  57. package/knowledge/skills/og-game/SKILL.md +1479 -0
  58. package/knowledge/skills/og-i18n/SKILL.md +220 -0
  59. package/knowledge/skills/og-platform-sdk/SKILL.md +199 -0
  60. package/knowledge/skills/og-taste/SKILL.md +537 -0
  61. package/knowledge/skills/og-ui/SKILL.md +444 -0
  62. package/knowledge/wiki/ai.md +627 -0
  63. package/knowledge/wiki/anim.md +1187 -0
  64. package/knowledge/wiki/atmosphere.md +636 -0
  65. package/knowledge/wiki/audio.md +732 -0
  66. package/knowledge/wiki/camera.md +711 -0
  67. package/knowledge/wiki/commands.md +357 -0
  68. package/knowledge/wiki/game.md +220 -0
  69. package/knowledge/wiki/i18n.md +213 -0
  70. package/knowledge/wiki/index.md +1027 -0
  71. package/knowledge/wiki/input.md +1714 -0
  72. package/knowledge/wiki/load.md +1280 -0
  73. package/knowledge/wiki/math.md +166 -0
  74. package/knowledge/wiki/nav.md +1154 -0
  75. package/knowledge/wiki/physics.md +3142 -0
  76. package/knowledge/wiki/platform.md +857 -0
  77. package/knowledge/wiki/probe.md +1692 -0
  78. package/knowledge/wiki/reflect.md +285 -0
  79. package/knowledge/wiki/render.md +3511 -0
  80. package/knowledge/wiki/rng.md +393 -0
  81. package/knowledge/wiki/stream.md +376 -0
  82. package/knowledge/wiki/system.md +197 -0
  83. package/knowledge/wiki/systems.md +43 -0
  84. package/knowledge/wiki/terrain.md +727 -0
  85. package/knowledge/wiki/tilemap.md +422 -0
  86. package/knowledge/wiki/time.md +150 -0
  87. package/knowledge/wiki/transform.md +230 -0
  88. package/knowledge/wiki/tween.md +580 -0
  89. package/knowledge/wiki/ui.md +8000 -0
  90. package/knowledge/wiki/world.md +909 -0
  91. package/package.json +68 -4
  92. package/schema/api.schema.json +29675 -0
  93. package/shell/README.md +22 -0
  94. package/shell/brand/README.md +23 -0
  95. package/shell/brand/icon-192.png +0 -0
  96. package/shell/brand/icon-512.png +0 -0
  97. package/shell/brand/mark-on-paper.svg +6 -0
  98. package/shell/brand/origin-mark.svg +5 -0
  99. package/shell/brand/origin-motion.css +251 -0
  100. package/shell/brand/tokens.css +20 -0
  101. package/shell/brand.ts +132 -0
  102. package/shell/page.ts +715 -0
  103. package/shell/platform.ts +203 -0
  104. package/shell/player.ts +238 -0
  105. package/shell/shell.css +185 -0
  106. package/shell/sw.ts +177 -0
  107. package/shell/transport.ts +515 -0
  108. package/shell/wire.ts +369 -0
  109. package/src/e6.ts +69 -0
  110. package/src/env.d.ts +100 -0
  111. package/src/generated/abi.ts +980 -0
  112. package/src/index.ts +193 -0
  113. package/src/node/audio.ts +86 -0
  114. package/src/node/build.ts +109 -0
  115. package/src/node/device.ts +981 -0
  116. package/src/node/doctor.ts +60 -0
  117. package/src/node/index.ts +102 -0
  118. package/src/node/platform.ts +24 -0
  119. package/src/node/playtest-run.ts +249 -0
  120. package/src/node/png.ts +130 -0
  121. package/src/node/polish.ts +976 -0
  122. package/src/node/run.ts +373 -0
  123. package/src/node/web-build.ts +808 -0
  124. package/src/p2.ts +35 -0
  125. package/src/platform-local.ts +211 -0
  126. package/src/playtest.ts +131 -0
  127. package/src/runtime.ts +1509 -0
  128. package/src/systems/cameras.ts +247 -0
  129. package/src/ui-kit/bind.ts +141 -0
  130. package/src/ui-kit/huds.ts +303 -0
  131. package/src/ui-kit/index.ts +36 -0
  132. package/src/ui-kit/menus.ts +187 -0
  133. package/src/ui-kit/settings.ts +131 -0
  134. package/src/ui-kit/store.ts +222 -0
  135. package/src/ui-kit/story.ts +89 -0
  136. package/src/ui-kit/util.ts +106 -0
  137. package/src/web/a11y.ts +429 -0
  138. package/src/web/audio-worklet.ts +193 -0
  139. package/src/web/audio.ts +307 -0
  140. package/src/web/basis.ts +239 -0
  141. package/src/web/gamepad.ts +123 -0
  142. package/src/web/host.ts +166 -0
  143. package/src/web/input.ts +237 -0
  144. package/src/web/lanes.ts +21 -0
  145. package/src/web/play.ts +20 -0
  146. package/src/web/rpc-ring.ts +110 -0
  147. package/src/web/settle.ts +122 -0
  148. package/src/web/shell.ts +643 -0
  149. package/src/web/text-input.ts +248 -0
  150. package/src/web/worker.ts +1002 -0
  151. package/types/bindings.json +13654 -0
  152. package/types/index.d.ts +20090 -0
  153. package/vendor/basis/LICENSE +201 -0
  154. package/vendor/basis/NOTICE +15 -0
  155. package/vendor/basis/README.md +18 -0
  156. package/vendor/basis/basis_transcoder.js +19 -0
  157. package/vendor/basis/basis_transcoder.wasm +0 -0
  158. package/README.md +0 -3
@@ -0,0 +1,385 @@
1
+ ---
2
+ name: og-feel
3
+ description: Game feel for an AI making a game on Origin Engine — input that answers on the same tick, anticipation and recovery, hit stop by impact weight, screen shake trauma and when not to shake, squash and stretch amounts, easing by intent, floating text, camera look-ahead, dead zones and zones, 2D frame counts and rates by action, sprite sorting, parallax ratios, platformer and character controller tunables by genre, and a checklist proved with og run --commands captures and world dumps. Use when a game works but feels flat, when adding hits, jumps, pickups or a camera, or before calling a 2D game done.
4
+ ---
5
+
6
+ A game feels right when every input shows an answer at once, every impact
7
+ has weight, and the camera helps the player read what comes next. On
8
+ Origin Engine all of it is deterministic data: tweens, flipbooks, the time
9
+ scale, camera trauma and floating texts live in the world's state blocks,
10
+ so a replay shows the same juice and `og test` can check it. This skill's
11
+ modules are `reference/time.md` (time scale, hit stop), `reference/tween.md`,
12
+ `reference/camera.md` and `reference/systems.md` (the camera controllers the
13
+ engine ships in TypeScript); sprites, flipbooks, tilemaps and floating text
14
+ are og-game's (§8, §10). This skill is how much of each to use, and how to
15
+ prove it.
16
+
17
+ ## What works
18
+
19
+ In Hollowbell (`examples/hollowbell`, a 2D platformer written from this
20
+ package alone) `og polish` measured every input answered within a tick and every damage moment with hit stop,
21
+ shake, a number and a sound (feel 92; Lantern Vale, a 3D adventure, 90).
22
+ An `og.CameraZone` held its boss arena, and flipbook frame events put the
23
+ footsteps and the strikes on their exact frames. The Kestrel Stair
24
+ (`examples/kestrel-stair`, a 3D third-person game) measured the same in
25
+ `og polish` (input latency and hit feedback 100; feel 79, the rest lost to
26
+ frame time): hit stop by weight, a shake, a number and a sound on every
27
+ strike landing on its clip's `hit` marker, and the 3D character controller
28
+ at full speed every tick (3.2 m/s walking) over level ground, an 18° ramp,
29
+ a 34° path and stairs, refusing a 63° scree.
30
+
31
+ ## 1. Answer on the same tick
32
+
33
+ - Read input in a system and act in **the same tick**: start the jump, the
34
+ swing, the step. Never queue a press for "the next frame" or wait for an
35
+ animation to finish before moving. At 60 Hz one tick is 17 ms; two ticks
36
+ of delay are felt.
37
+ - Buffer early presses (a jump pressed up to 6 ticks before landing jumps
38
+ on landing) and allow coyote time (a jump up to 6 ticks after leaving a
39
+ ledge). Keep the counters in a component so snapshots carry them.
40
+ - Move gameplay by `time.scaledDt` (the systems' `time`): slow motion and
41
+ hit stop then reach your code the same way they reach physics, og.anim,
42
+ flipbooks and tweens. Use `time.dt` only for what must keep its pace
43
+ (menus, input buffers, the UI).
44
+ - Prove it: `0.5:key_down:Space;0.517:dump_entity:hero:a.json` — the dump
45
+ of the tick after the press already shows the new velocity or clip.
46
+
47
+ ## 2. Anticipation, action, recovery
48
+
49
+ Every action has three beats; the action itself is the shortest.
50
+
51
+ | Beat | Frames at 60 Hz | How |
52
+ |---|---|---|
53
+ | Anticipation | 0 for player-driven moves (latency wins), 4–12 for enemies and big attacks | a wind-up clip or `og.tween.to` with `backIn` on scale or position |
54
+ | Action | 1–4 | the frame the hit lands: fire the hit on a flipbook `event` (`event 2 "hit"`), not on a timer |
55
+ | Recovery | 6–20 | a recover clip, `og.tween.squash` easing back, a settle with `backOut` |
56
+
57
+ The player's own attacks skip anticipation; telegraph enemy attacks with
58
+ it (a flash, a crouch, a 0.25–0.5 s wind-up) so the player can react.
59
+
60
+ ## 3. Hit stop by impact weight
61
+
62
+ `og.time.hitStop(world, ticks)` freezes physics, og.anim, flipbooks and
63
+ scaled tweens while input, the UI, unscaled tweens and camera shake keep
64
+ moving. It never shortens one already running.
65
+
66
+ | Impact | Ticks at 60 Hz |
67
+ |---|---|
68
+ | Light (a jab, a coin, a small enemy hit) | 2–3 |
69
+ | Medium (a sword hit, a stomp, a 4-match) | 4–6 |
70
+ | Heavy (a charged attack, a boss hit, a big cascade) | 8–12 |
71
+ | Finishing blow, a death | 12–20, paired with slow motion |
72
+
73
+ - Pair a hit stop with a flash (`og.tween.flash`, 0.08–0.15 s) and a small
74
+ shake; the stop is what sells the contact. The flash takes a sprite, a
75
+ mesh or a glTF instance (its og.Model: that instance alone flashes, the
76
+ others sharing its materials do not). On an og.Model the tween sets and fades
77
+ its `flash` (the lit colour mixed towards `flashColor`); its `tint`
78
+ colours that one instance for as long as it is set.
79
+ - Pair it with a knock-back too: on the hit's tick set the
80
+ character's `og.PlatformerController.impulse` to `{ x: away * 6, y: 7 }`
81
+ (m/s; taken once on the next step, so it flies when the stop ends) and
82
+ hold its `moveX` at 0 for 10–15 ticks; a dynamic body takes it as
83
+ `og.Velocity` (`linear` away and up).
84
+ - Slow motion: `og.time.setScale(world, 0.3)` for a last kill or a near
85
+ miss, then back to 1 after 0.5–1 s of real time (count ticks, not
86
+ scaled time). Never leave the scale below 1 by accident: assert it in a
87
+ test.
88
+ - Where it lives: `world.resource(og.Time)` and a system's `time` carry
89
+ `scale`, `hitStop`, `scaledDt` and `scaledElapsed` beside `tick`, `dt`,
90
+ `elapsed` and `rate`. The world keeps the scale in a second resource,
91
+ `og.TimeScale` (`scale`, `hitStop`, `elapsed`), which is what a world
92
+ dump lists; read og.Time in code, and `get:og.Time` (every field above)
93
+ or `set:og.TimeScale.scale=0.5` from a script.
94
+ - Do not hit-stop on every bullet of a fast weapon or every coin of a
95
+ stream; cap it to one stop per 10 ticks.
96
+
97
+ ## 4. Screen shake: trauma, not offsets
98
+
99
+ `og.camera.shake(world, trauma)` adds trauma (0..1) to og.Camera, which
100
+ decays by `traumaDecay` (1.5 per second) on the unscaled tick. The offset
101
+ and roll grow with trauma squared, so small values stay subtle.
102
+
103
+ | Event | Trauma |
104
+ |---|---|
105
+ | A light hit, landing from a high jump | 0.1–0.2 |
106
+ | A medium hit taken, a big match | 0.25–0.4 |
107
+ | An explosion, a boss slam, the player's death | 0.5–0.8 |
108
+ | 1.0 | almost never: it is nauseating |
109
+
110
+ - Shake the camera for what hurts or hits hard; **never** for UI, pickups,
111
+ menus, steady events (engine hum, rain), or every shot of a rapid weapon.
112
+ A game that shakes all the time has no shake left for the moments that
113
+ count. A refused press or a wrong answer shakes the element, not the
114
+ camera (`og.ui.shake`, og-ui §6).
115
+ - Prefer `og.camera.kick(world, direction, distance)` (0.1–0.3 units in the
116
+ direction of the force) for directional impacts and recoil, and
117
+ `og.camera.punch(world, 0.05–0.15)` for a zoom-in on a crit or a slam.
118
+ They are visual only (og.Camera's position stays put, the frame moves),
119
+ so they never change the simulation.
120
+ - Offer a reduced-motion setting that scales trauma, kick and punch by 0.3.
121
+
122
+ ## 5. Squash and stretch
123
+
124
+ `og.tween.squash(world, { entity, impact, duration, ease })` sets
125
+ og.Transform.scale to rest × impact on the tick it is called and eases
126
+ back (default backOut, 0.18 s). Keep the area roughly constant: x × y ≈ 1.
127
+ On a 3D model give z too, bulging with x so the volume holds: a hit taken
128
+ is `{ x: 1.15, y: 0.8, z: 1.15 }`. Left out (or 0), z stays at rest, which
129
+ is what a sprite wants.
130
+
131
+ | Moment | impact (x, y) | duration |
132
+ |---|---|---|
133
+ | Landing (small hop) | (1.15, 0.85) | 0.12 s |
134
+ | Landing (big fall) | (1.3, 0.7) | 0.18 s |
135
+ | Take-off | (0.85, 1.15) | 0.1 s |
136
+ | Hit taken | (1.2, 0.8) | 0.15 s, with a flash |
137
+ | Pickup collected (on the pickup) | (1.4, 1.4) then shrink to 0 | 0.15 s |
138
+ | UI button press (the skin's `press` does it for og.ui) | (0.92, 0.92) | 0.08 s, unscaled |
139
+
140
+ Do not squash rigid things (crates, walls, the camera's target in a
141
+ cutscene) or anything more than once per 6 ticks.
142
+
143
+ ## 6. Easing by intent
144
+
145
+ `og.tween.to(world, { entity, component, field: "position.y", to: [2],
146
+ duration, ease, delay, loops, yoyo })` animates any numeric component field;
147
+ `og.tween.sequence` and `og.tween.parallel` chain them, `og.TweenCompleted`
148
+ reports the end, and `unscaled: true` keeps a tween going through slow
149
+ motion and hit stop (UI motion). `og.tween.squash` (§5) and
150
+ `og.tween.flash` are the presets.
151
+
152
+ | Intent | Ease |
153
+ |---|---|
154
+ | Things arriving and settling (UI in, a gem landing) | `cubicOut`, `quartOut` |
155
+ | Things leaving (UI out, a dying enemy fading) | `cubicIn`, `expoIn` |
156
+ | A deliberate move from A to B (camera, a swap) | `cubicInOut` |
157
+ | A pop, a button, squash recovery | `backOut` |
158
+ | A wind-up (anticipation) | `backIn` |
159
+ | Springy, cartoon impact | `elasticOut` (sparingly: it rings for a while) |
160
+ | A drop that lands | `bounceOut` |
161
+ | Constant motion, timers, scrolling, colour cycles | `linear` |
162
+ | Most small motion when unsure | `quadOut` (the default) |
163
+
164
+ Durations: 0.1–0.2 s for feedback, 0.12–0.3 s for UI (the skin's
165
+ durations, og-taste §5), 0.2–0.35 s for swaps, 0.4–0.8 s for camera moves,
166
+ reveals and screen transitions (up to 0.7 s). Longer feels sluggish unless
167
+ it is a cutscene or a reward.
168
+
169
+ ## 7. Floating text
170
+
171
+ `og.render.floatingText(world, { text, position, ... })` pops a text that
172
+ rises, drifts and fades by itself (world state, deterministic).
173
+
174
+ - Damage and score numbers above the hit point, not on the HUD: the eye is
175
+ already there. Pickups: what was gained (`+1`, `key`), short.
176
+ - Colour by meaning and keep it consistent: white damage dealt, red damage
177
+ taken, yellow gold, green heal. Always an outline (the default 0.6) so it
178
+ reads on any background.
179
+ - Crits and combos: `crit: true` (critScale 1.6 and a stronger pop); the
180
+ biggest number on screen should be the most important.
181
+ - Lifetime 0.6–1.0 s, rise 0.8–1.5 units, spread 0.2–0.4 so stacked hits do
182
+ not overlap. At most 6–8 on screen: sum rapid hits into one number.
183
+
184
+ ## 8. Camera: look-ahead, dead zones, zones, rails
185
+
186
+ - `og.Follow2D` with a dead zone of 0.5–1.5 units horizontally (smaller
187
+ vertically) so small moves do not swim the screen; `smoothTime` 0.1–0.2 s,
188
+ `smoothTimeY` 0.2–0.35 s in platformers (follow jumps lazily, land
189
+ firmly).
190
+ - Look-ahead: `lookAhead` 0.15–0.3 s with `lookAheadMax` 2–4 units in the
191
+ running direction; the player should see more of where they go than of
192
+ where they were. Runners and shooters lean hard ahead; top-down games
193
+ lightly.
194
+ - `og.CameraZone` per room or arena keeps the view inside it (applied to
195
+ the frame, from the real aspect, so the simulation stays independent of
196
+ the screen); a zone smaller than the view centres it. Its `scrollTime`
197
+ (0.3–0.6 s, eased in and out) scrolls the view from the room the player
198
+ left into this one when they cross; 0 cuts at once. og.Camera's
199
+ `zoneScroll` says how far a transition has come.
200
+ - `og.CameraRail` for corridors and set pieces: the camera rides a
201
+ Catmull-Rom spline to the point nearest the player.
202
+ - 3D: `og.OrbitCamera` (drag to turn, wheel to zoom) and `og.FollowCamera`
203
+ on the entity the camera orbits or follows, driven by
204
+ `og.systems.cameras` listed in `GameDesc.systems`.
205
+ - The camera never moves on its own during play except for shake, kick,
206
+ punch and a zone's scroll. Do not tween the camera while the player has
207
+ control.
208
+
209
+ ## 9. 2D animation: frames and rates
210
+
211
+ Flipbooks (`og.Flipbook` on an entity with `og.Sprite`) play by scaled time,
212
+ fire `og.FlipbookEvent` on frames named in the sheet, and loop, ping-pong
213
+ or play once.
214
+
215
+ | Action | Frames | fps |
216
+ |---|---|---|
217
+ | Idle (breathing) | 2–4 | 4–6 (or ping-pong) |
218
+ | Walk | 4–8 | 8–12 |
219
+ | Run | 6–8 | 12–16 |
220
+ | Jump (up, apex, fall) | 1 each, chosen by vertical speed | — |
221
+ | Attack (wind-up, hit, recover) | 3–6, once | 12–20, `event` on the hit frame |
222
+ | Hurt | 1–2, once | 8–12, with a flash |
223
+ | Death | 4–8, once | 8–12 |
224
+ | Pickups, coins | 4–8 loop | 8–12 |
225
+
226
+ - Put footsteps and hits on `event` lines, never on timers: they stay in
227
+ step under slow motion and speed changes.
228
+ - Match the walk rate to the move speed (the feet should not skate):
229
+ `Flipbook.speed = speed / walkSpeed`.
230
+ - Face with `og.Sprite.flipX` instead of drawing left-facing frames; keep
231
+ the pivot at the feet (`pivot 0.5 0`).
232
+ - Pixel art: `texture { kind pixel }` import settings (lossless, nearest)
233
+ and whole-pixel scales: ortho `height` = visible tiles; one tile one unit.
234
+ - Skinned 3D characters can hold poses the same way: `og.AnimationPlayer`'s
235
+ `stepFps` 12 animates on twos and 8 on threes, so clay, puppet and paper
236
+ styles read as stop motion while the character still moves smoothly
237
+ through the world (only the pose steps; events and root motion stay
238
+ continuous). Keep the camera and effects smooth: stepping everything
239
+ reads as a low frame rate, not a style.
240
+
241
+ ## 10. Sprite sorting and layers
242
+
243
+ - Layers by role: tiles −10…−1 (`og.Tilemap.layer`), shadows and the floor
244
+ decals 0, characters and props 0 with `sortY`, effects 10–20, floating
245
+ text 100, the HUD in screen space.
246
+ - Top-down: `sortY: true` on everything that stands (characters, trees,
247
+ pillars) and the pivot at the feet, so lower on the screen draws in
248
+ front. Wall tops and roofs go in a tile layer above the characters.
249
+ - Blob shadows (`og.Sprite.shadow` 0.3–0.45) ground characters and
250
+ pickups; they draw under every sprite of their layer.
251
+ - Trails on 2D sprites: `og.Trail` with `layered: true` and the layer
252
+ below the sprite's. Afterimages (`og.Afterimage`, 3–5 ghosts every 2–3
253
+ ticks) for dashes and speed bursts only.
254
+
255
+ ## 11. Parallax
256
+
257
+ Tile layers scroll by their `parallax` (1 with the world, 0 with the
258
+ camera); repeat far layers along x (`repeat x`).
259
+
260
+ | Layer | parallax x | parallax y |
261
+ |---|---|---|
262
+ | Sky, far gradient | 0–0.05 | 0 |
263
+ | Distant skyline, mountains | 0.1–0.25 | 0.05–0.1 |
264
+ | Mid buildings, hills | 0.4–0.6 | 0.2–0.4 |
265
+ | Play layer | 1 | 1 |
266
+ | Foreground (out of focus, sparse) | 1.2–1.5 | 1–1.2 |
267
+
268
+ Keep parallax backgrounds darker and less saturated than the play layer,
269
+ and never put gameplay on a layer whose parallax is not 1.
270
+
271
+ ## 12. Controllers by genre
272
+
273
+ Use `og.PlatformerController` (2D), `og.CharacterController` (3D) and
274
+ `og.Vehicle` (cars) instead of moving bodies by hand: the controllers
275
+ already have coyote time, a jump buffer, variable jump height and moving
276
+ platforms, the car its suspension, grip and steering. Tune by feel,
277
+ starting from these (the defaults are the first column):
278
+
279
+ | `og.PlatformerController` | precise (Celeste-like) | floaty (kid-friendly) | heavy (Castlevania-like) |
280
+ |---|---|---|---|
281
+ | `maxSpeed` (m/s) | 8 | 6 | 5 |
282
+ | `acceleration` / `deceleration` | 70 / 80 | 30 / 25 | 120 / 160 (snappy, committed) |
283
+ | `airAcceleration` / `airDeceleration` | 45 / 25 | 20 / 10 | 15 / 5 |
284
+ | `jumpHeight` (m) / `timeToApex` (s) | 3.2 / 0.4 | 3.5 / 0.55 | 2.4 / 0.35 |
285
+ | `fallGravity` (× rising) | 1.8 | 1.2 | 2.4 |
286
+ | `jumpCut` (× gravity after release) | 2.5 | 1.5 | 1 (no cut: a fixed arc) |
287
+ | `apexGravity` / `apexThreshold` | 0.6 / 1.5 | 0.4 / 2 | 1 / 0 |
288
+ | `coyoteTime` / `jumpBuffer` (s) | 0.1 / 0.12 | 0.15 / 0.2 | 0.05 / 0.08 |
289
+ | `airJumps`, `wallJumps` | 0, 1 | 1, 0 | 0, 0 |
290
+
291
+ - Size the jump to the level: `jumpHeight` 0.5 m above the highest ledge a
292
+ player must reach, so a perfect press is not required.
293
+ - `cornerCorrection` (0.15 m) forgives head bumps; keep it under a third of
294
+ the character's width.
295
+ - 3D characters: `maxSlope` 45° (55° for arcade), `stepHeight` a third of
296
+ the capsule's height, `snapDistance` 0.2–0.3 m so stairs down do not
297
+ bounce, `jumpSpeed` from the height you want: `sqrt(2 × 9.81 × h)`.
298
+ - Read `og.CharacterEvent` for juice: `CHARACTER_LANDED` with its `speed`
299
+ scales the landing squash and dust; `CHARACTER_JUMPED` plays the jump
300
+ stretch (§5) and sound on the same tick.
301
+ - Ragdolls: `blendTime` 0.15–0.3 s from the animation to the bones; set
302
+ `weight` 1 on the killing blow's tick, with its hit stop, so the fall
303
+ starts from the hit pose.
304
+ - Cars: `og.Vehicle` on a dynamic chassis whose `og.RigidBody.mass` is the
305
+ car's (the defaults are a 1,000 kg road car; a car-sized box at density 1
306
+ weighs a few kilograms and the newtons below would throw it). The
307
+ suspension and `grip` scale with the mass; `engineForce` (per driven
308
+ wheel) and `brakeForce` (per wheel) are newtons and do not, so a heavier
309
+ car takes proportionally more. Measured on flat ground, 60 Hz:
310
+
311
+ | `og.Vehicle` | road car (the defaults) | arcade racer | truck | kart |
312
+ |---|---|---|---|---|
313
+ | chassis mass (kg), box (m) | 1,000, 1.8 × 0.6 × 4 | 1,000, 1.8 × 0.4 × 4 | 4,000, 2.4 × 1.6 × 6 | 150, 1 × 0.3 × 1.6 |
314
+ | `centerOfMass` y (m) | 0 | -0.3 | -0.4 | 0 |
315
+ | `wheelBase` / `track` / `wheelRadius` | 2.6 / 1.6 / 0.4 | 2.6 / 1.6 / 0.4 | 4 / 2 / 0.5 | 1.2 / 1 / 0.15 |
316
+ | `engineForce` (N a wheel), `drive` | 2,500, rear | 5,200, rear | 3,000, all | 450, rear |
317
+ | `brakeForce` (N a wheel) | 2,000 | 2,600 | 5,000 | 350 |
318
+ | `maxSpeed` (m/s) | 40 | 46 | 25 | 22 |
319
+ | `grip` | 2 (holds the line, slides at full lock) | 0.62 (slides on the throttle) | 2 | 2.5 |
320
+ | `maxSteer` (rad) | 0.55 | 0.5 | 0.45 | 0.6 |
321
+ | measured: after 1 s, braking | 4.7 m/s, 8 m/s² | 9.3 m/s, 10.5 m/s² | 2.8 m/s, 5 m/s² | 5 m/s, 10 m/s² |
322
+
323
+ `rollInfluence` 0 (the default) keeps every one of them on its wheels at
324
+ full lock at top speed; rapier's own 0.1 tips the road car onto its roof
325
+ at full lock above about 20 m/s. For body roll raise it to 0.05 with the
326
+ centre of mass lowered below the wheel mounts (`og.RigidBody.centerOfMass`).
327
+ Shorten the lock with speed (a quarter of it at top speed) and ease the
328
+ wheel to it over 0.1–0.2 s, as a pad's stick does; a little downforce
329
+ (`og.physics.applyForce(world, car, { x: 0, y: -0.004 * v * v * mass, z: 0 })`
330
+ every tick with the wheels down) keeps a fast car planted over crests.
331
+ Smoke, skid marks and the squeal read `og.Vehicle.slip` (the largest of
332
+ the wheels', 0 gripping to 1) or each wheel's `slip` and `sideSpeed` from
333
+ `og.physics.vehicleWheels`: start smoke and marks above about 0.2, fade
334
+ the squeal in with it. Prove it: with the throttle held from rest,
335
+ `get:car.og.Vehicle.speed` two seconds later reads about 8.9 m/s for the
336
+ defaults (4.7 after one).
337
+ - Prove it: `0.5:key_down:Space;0.9:dump_entity:hero:apex.json` — the dump
338
+ shows `og.PlatformerController.jumping` and a height within 10 % of
339
+ `jumpHeight` at `timeToApex`. `templates/archetypes/runner`'s courier is a
340
+ PlatformerController with its tuning in a `Tuning` resource;
341
+ `templates/archetypes/topdown`'s hero is a CharacterController in its
342
+ top-down mode.
343
+
344
+ ## 13. The checklist, proved
345
+
346
+ Make each a playtest (`tests/*.og.ts`) or at least a scripted run whose
347
+ outputs you read. A world dump lists resources by name only; read their
348
+ values with `get:` (og-build §1), which logs the value and writes it as JSON:
349
+
350
+ 1. **Same-tick response**: `t:key_down:X;t+1/60:dump_entity:hero:a.json`
351
+ shows the new state on the next tick.
352
+ 2. **Hit lands with weight**: during a hit, `dump_entity:enemy` shows
353
+ `og.Sprite.flash` > 0 and its position equal across the hit-stop ticks;
354
+ `get:og.Time` shows `hitStop` > 0 on the stop's first tick.
355
+ 3. **Shake decays**: `get:og.Camera.trauma` on the hit and about a second
356
+ later shows it rising, then back to 0.
357
+ 4. **Feet do not skate, steps are heard**: count og.FlipbookEvent `step`
358
+ events over a scripted walk (a game resource or a fact).
359
+ 5. **Floating text appears**: a `screenshot` 0.2 s after a hit shows the
360
+ number above the hit point (read the PNG).
361
+ 6. **The camera leads**: two screenshots while running show more space ahead
362
+ of the hero than behind; a dump shows `og.Follow2D.lead` non-zero.
363
+ 7. **Sorting**: a screenshot where a character walks behind and in front of
364
+ a pillar.
365
+ 8. **Determinism**: a playtest with `runs: 2` and
366
+ `t.equal(run.hashes[1], run.hashes[0], ...)` over a session with hits,
367
+ pickups and a hit stop: the juice must replay exactly.
368
+ 9. **No stray slow motion**: `get:og.Time.scale` at the end of the run is 1.
369
+ 10. **Rooms scroll**: `get:og.Camera` on the ticks after the hero crosses a
370
+ doorway shows `zoneScroll` rising from 0 to 1 (topdown's `door.og.ts`).
371
+
372
+ `templates/archetypes/topdown`, `runner` and `match3` do all of this, and
373
+ `tower-defense`, `roguelike`, `idle` and `rhythm` too (each README's feel
374
+ table names where); read their `src/` and `tests/` for working patterns.
375
+ For a 3D action game, `templates/archetypes/third-person` is the pattern: a
376
+ CharacterController over a level, clips retargeted at cook and run time
377
+ through blend trees, and navmesh enemies on behaviour machines whose
378
+ grabs land with hit stop and shake.
379
+
380
+ ## 14. Feel, measured: `og polish`
381
+
382
+ `og polish .` scores this checklist's measurable part (input latency, hit
383
+ feedback, idle frames, sound on input, frame time) next to og-taste's;
384
+ og-build §5 is the one list of its measures, thresholds and fixes. Expose
385
+ `hp`, `hits` or `kills` as facts (og-game §3) or the hit measures stay n/a.