@genex-ai/cli-demo 0.68.0-dev.174 → 0.69.0-dev.175

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.68.0-dev.174",
3
+ "version": "0.69.0-dev.175",
4
4
  "description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,9 +35,12 @@ rules, floating-origin shot, pointer controls, and implementation limits.
35
35
 
36
36
  ## Aiming and pointer lock
37
37
 
38
- On the bundled `FollowCamera`, **pointer-lock aim is ON by default** on desktop:
39
- a click locks the pointer and raw mouse movement drives the view. You rarely turn
40
- it on you decide whether to turn it OFF. Name the bucket in the build plan:
38
+ The rule is binary: during play, **the cursor is either a gameplay tool or it is
39
+ locked away**. An OS arrow parked over the action in a game that never uses it is
40
+ a shipped defect, not a default. On the bundled `FollowCamera`, pointer-lock aim
41
+ is ON by default on desktop — a click locks the pointer and raw mouse movement
42
+ drives the view — so mostly you decide whether to turn it OFF. Name the bucket in
43
+ the build plan:
41
44
 
42
45
  - **MANDATORY** — first-person of any kind (FPS, walking sim, horror) and any
43
46
  mouse-aimed action (third-person shooter, turret/range). Lock is on by default;
@@ -47,11 +50,16 @@ it on — you decide whether to turn it OFF. Name the bucket in the build plan:
47
50
  `genex controller character` game). On by default; leave it on. Opt out with
48
51
  `pointerLockAim: false` only for a stated reason (a cursor-heavy UI at the core
49
52
  of play).
50
- - **NEVER** — cursor-core games (top-down click-to-move, tower defense, builders,
51
- card/board/puzzle) and spectator/orbit showcases. These **must pass
52
- `pointerLockAim: false`** otherwise the bundled camera grabs the cursor on the
53
- first click. (Touch needs nothing: pointer lock doesn't exist there and the mode
54
- no-ops on coarse pointers.)
53
+ - **Keyboard-driven games lock too** — a racer, platformer, or runner that never
54
+ reads the mouse still locks the pointer on the play/Start click: the cursor is
55
+ not a tool there, so lock it away (hidden cursor, no stray clicks, Esc = pause
56
+ as usual). Bundled-controller games get this for free; a hand-rolled game uses
57
+ the minimal lock recipe in `$genex-threejs-game-ui` (~6 lines).
58
+ - **NEVER** — cursor-core games where the pointer IS the gameplay tool (top-down
59
+ click-to-move, tower defense, builders, card/board/puzzle) and spectator/orbit
60
+ showcases. These **must pass `pointerLockAim: false`** — otherwise the bundled
61
+ camera grabs the cursor on the first click. (Touch needs nothing: pointer lock
62
+ doesn't exist there and the mode no-ops on coarse pointers.)
55
63
 
56
64
  **Mechanism — games on the bundled controller (most games):** do NOT hand-roll
57
65
  lock handling. Aim is already enabled; the kit ships a ready-made cue overlay —
@@ -113,6 +121,36 @@ grants the permission.
113
121
  (trackpad "natural scrolling" only flips the wheel). If look feels inverted it's a
114
122
  sign bug in the rig, not a device quirk — fix the sign, don't sniff the trackpad.
115
123
 
124
+ ## The screen-direction contract
125
+
126
+ Every input axis has one correct on-screen direction, for every rig, bundled or
127
+ hand-rolled. These four invariants are testable and non-negotiable:
128
+
129
+ 1. Mouse/touchpad RIGHT turns the view right; mouse UP looks up (down only behind
130
+ an explicit invert option the player chose).
131
+ 2. `KeyD`/ArrowRight moves or turns the player toward screen-RIGHT; `KeyA`/
132
+ ArrowLeft toward screen-left. Same for a touch stick's +x.
133
+ 3. Drag-pan picks ONE convention — grab-the-world (terrain follows the pointer)
134
+ or move-the-camera — and BOTH axes obey it. One axis each is the
135
+ "diagonals feel twisted" bug.
136
+ 4. See the OS-setting note above: inversion is always your sign, never the device.
137
+
138
+ The formula that settles every sign argument: `screenRight = cross(cameraForward, worldUp)`.
139
+ For a Y-up world and forward `(sin yaw, 0, cos yaw)`, screen-right is
140
+ `(-cos yaw, 0, sin yaw)`. **Warning — `(cos yaw, 0, -sin yaw)` is the LEFT
141
+ vector** (that's `cross(worldUp, cameraForward)`), and writing it as "right" is
142
+ the single most-shipped direction bug in generated games: two independent
143
+ projects inverted their A/D exactly this way. Related trap: positive
144
+ `rotation.y` turns a +Z-facing object toward +X, which is screen-LEFT from a
145
+ chase camera behind it — so "positive yaw = turn right" is false in this basis.
146
+
147
+ Never derive signs by intuition — intuition about right-handed frames is wrong
148
+ about half the time and has been wrong in every shipped instance. Copy a
149
+ verified pair (sign AND basis together) from
150
+ [references/camera-rigs.md](references/camera-rigs.md), then confirm with the
151
+ input-direction part of the smoke check: hold D and watch which way the world
152
+ answers.
153
+
116
154
  ## Non-negotiable rules
117
155
 
118
156
  - Use subject dimensions to derive offsets; do not tune one fixed distance for
@@ -122,6 +160,8 @@ sign bug in the rig, not a device quirk — fix the sign, don't sniff the trackp
122
160
  - During an explicit handoff, use one interpolation stage. Do not stack a
123
161
  transition blend and a second follow smoother over the same interval.
124
162
  - Re-sync yaw/pitch from the camera when pointer lock is acquired.
163
+ - Hand-rolled steering/pan/look math copies a verified basis from the reference
164
+ and passes the input-direction check — signs are never derived by intuition.
125
165
  - Update the projection matrix whenever FOV, near, far, or aspect changes.
126
166
  - Keep stars or infinite backgrounds camera-relative when large translation
127
167
  would create false parallax or precision loss.
@@ -11,6 +11,7 @@ Use this reference for scale-aware chase, side, orbit, authored-shot, pointer-lo
11
11
  - Explicit camera handoffs
12
12
  - cinematic implementation shot ownership
13
13
  - Pointer-look and movement constraints
14
+ - Verified screen-direction bases
14
15
  - Floating origin and background handling
15
16
  - Projection and lifecycle ownership
16
17
  - Failure modes and diagnostics
@@ -166,6 +167,9 @@ yaw -= mouseDeltaX * 0.0022
166
167
  pitch -= mouseDeltaY * 0.0018
167
168
  ```
168
169
 
170
+ Expected on screen: mouse-right orbits the view right, mouse-up tilts it up —
171
+ verify both axes against the screen-direction contract before retuning the scales.
172
+
169
173
  Pitch bounds vary by flight mode. The implementation also enforces camera height above
170
174
  the ship:
171
175
 
@@ -281,6 +285,9 @@ distance = movementSpeed * dt
281
285
 
282
286
  Default speed is `9`, sensitivity `0.0023`.
283
287
 
288
+ Expected on screen: mouse-right turns the view right, mouse-up looks up — assert
289
+ both axes (yaw-only evidence has let inverted pitch ship).
290
+
284
291
  Keys are cleared on:
285
292
 
286
293
  - pointer-lock exit;
@@ -295,6 +302,61 @@ Scene-specific constraints then run after controls:
295
302
 
296
303
  Input control and spatial constraint are separate layers.
297
304
 
305
+ ## Verified screen-direction bases
306
+
307
+ Copy these pairs whole — the sign and the basis are only correct TOGETHER. Each
308
+ was derived from `screenRight = cross(cameraForward, worldUp)` and verified
309
+ against the on-screen result; if you change one half, re-verify with the
310
+ input-direction check instead of reasoning about it.
311
+
312
+ **Chase-cam steering** (vehicle/character heading, camera behind):
313
+
314
+ ```text
315
+ heading = (sin yaw, 0, cos yaw) // matches rotation.y for a +Z-front model
316
+ yaw -= steer * rate * dt // steer: D/right = +1, A/left = -1
317
+ position += heading * speed * dt
318
+ camera at position - heading * dist, lookAt(position)
319
+ ```
320
+
321
+ Why the minus: the camera looks along `heading`, so screen-right is
322
+ `cross(heading, up) = (-cos yaw, 0, sin yaw)`, while `d(heading)/d(yaw) =
323
+ (cos yaw, 0, -sin yaw)` — exactly screen-LEFT. Increasing yaw always veers the
324
+ nose left on screen, so "D turns right" needs `yaw -=`. (Equivalently
325
+ `yaw += steer` is correct only with heading `(-sin yaw, 0, cos yaw)` — a pair,
326
+ never a lone sign.)
327
+
328
+ **RTS / overhead pan camera** (fixed pitch, yaw-orbiting):
329
+
330
+ ```text
331
+ right = (-cos yaw, 0, sin yaw) // pitch-independent screen-right on the ground
332
+ forwardGround = (sin yaw, 0, cos yaw) // into the screen along the ground
333
+ D / ArrowRight: target += right * pan A / ArrowLeft: target -= right * pan
334
+ W / ArrowUp: target += forwardGround * pan S: target -= forwardGround * pan
335
+ ```
336
+
337
+ Drag-pan, grab-the-world (terrain follows the pointer; both axes, one
338
+ convention — `movementY` is positive DOWNWARD):
339
+
340
+ ```text
341
+ target -= right * movementX * k
342
+ target += forwardGround * movementY * k
343
+ ```
344
+
345
+ Move-the-camera convention = flip BOTH signs, never one. The classic shipped bug
346
+ writes `right = (cos yaw, 0, -sin yaw)` — that is `cross(up, forward)`, the LEFT
347
+ vector — inverting A/D and the horizontal drag while W/S stay correct.
348
+
349
+ **Pointer-look** (locked mouse driving yaw/pitch, Euler order `YXZ`):
350
+
351
+ ```text
352
+ yaw -= movementX * sensitivity // mouse-right -> view turns RIGHT
353
+ pitch -= movementY * sensitivity // mouse-up -> view tilts UP
354
+ ```
355
+
356
+ Expected on screen, both axes, before any tuning: a straight-ahead landmark
357
+ slides LEFT when the mouse moves right (the view pans right) and slides DOWN
358
+ when the mouse moves up (the view tilts up).
359
+
298
360
  ## Floating origin and background handling
299
361
 
300
362
  The Saturn scene first computes a virtual camera pose, stores its orientation
@@ -37,7 +37,10 @@ no matter how good it looks.
37
37
  unlocked drag-to-turn camera feels imprecise no matter how tight the numbers
38
38
  are. The bucket rule + the bundled `FollowCamera` aim mode live in
39
39
  `$genex-threejs-camera-direction`; on the bundled controller it's ON by default
40
- (with a ready-made cue), not hand-rolled events.
40
+ (with a ready-made cue), not hand-rolled events. Direction is half of it: a
41
+ movement key or look axis whose on-screen direction contradicts its label is a
42
+ defect, not a tuning issue — the screen-direction contract and verified bases
43
+ are in `$genex-threejs-camera-direction`.
41
44
 
42
45
  ## Movement: snappy beats realistic
43
46
 
@@ -250,8 +250,15 @@ document.addEventListener("keydown", (e) => {
250
250
  so ALSO keep the pointer-lock-loss → pause path (`pointerlockchange`: if
251
251
  unlocked while `playing`, `setPhase("paused")`). Escape then always pauses; it
252
252
  only *also* drops fullscreen on browsers without the lock — unavoidable there.
253
- - **Only for immersive/pointer-lock games.** A top-down or menu-driven game never
254
- captures the mouse and has no window to shrink — skip all of this.
253
+ - **Skip Keyboard Lock + fullscreen only when the cursor stays a tool.**
254
+ Cursor-core / top-down / menu-driven games never capture the pointer — skip
255
+ `enterImmersive` and Keyboard Lock entirely. But ANY game that locks the
256
+ pointer during play — aim games AND keyboard-only racers/platformers/runners
257
+ under the lock-or-tool rule (next section) — keeps the Escape → pause path and
258
+ the `pointerlockchange` fallback (if unlocked while `playing`, pause): without
259
+ it, Esc frees the cursor while the game keeps running. Keyboard-only games may
260
+ still skip Keyboard Lock + fullscreen; the lock + pause/resume path is the
261
+ non-negotiable part.
255
262
  - **Dashboard embed:** no setup needed — the platform's game frame grants keyboard
256
263
  lock (and pointer lock + fullscreen), so Escape-to-pause works the same inside
257
264
  `/world/` + `/draft/` as it does standalone (`<slug>.genex.technology`).
@@ -261,6 +268,28 @@ menu — trackpads feel slower than mice, so let the player tune it. On the bund
261
268
  camera the setter is live: `slider.oninput = () => { followCam.aimSensitivity = +slider.value; };`
262
269
  (radians per pixel; default `0.0023`, a usable range is ~`0.0008`–`0.005`).
263
270
 
271
+ ## The cursor during play: locked or a tool
272
+
273
+ During play the OS cursor is either the gameplay tool (cursor-core: click-to-move,
274
+ tower defense, builders, card/board — it stays visible, that's correct) or it is
275
+ **locked away — including keyboard-only games** (racer, platformer, runner): an
276
+ arrow parked over the action for the whole session is a shipped defect. Games on
277
+ the bundled `FollowCamera` get the lock free (on by default). A hand-rolled game
278
+ locks with ~6 lines, reusing this section's Escape flow:
279
+
280
+ ```ts
281
+ // In the Play/Start/Resume CLICK handler (lock needs a user gesture):
282
+ canvas.requestPointerLock?.(); // hides the cursor, focuses the game
283
+ // Already in the Escape recipe above: pointerlockchange → if unlocked while
284
+ // playing, pause; the Resume click re-locks. Keyboard games need nothing more —
285
+ // no reticle, no aim code; the lock just parks the cursor.
286
+ ```
287
+
288
+ If the lock is genuinely unavailable (a third-party embed without
289
+ `allow="pointer-lock"`), fall back to hiding the idle cursor over the canvas:
290
+ `canvas.style.cursor = "none"` after ~2s without `pointermove`, restored
291
+ instantly on move. Menus and pause screens always keep their cursor.
292
+
264
293
  ## The loader
265
294
 
266
295
  The loader is the first thing every player sees — a bare "Loading… 3/5" over
@@ -399,6 +428,9 @@ architecture and consume the shared style brief.
399
428
  - HUD sprites never enqueued at the gate — the placeholder CSS shipped as the
400
429
  final HUD.
401
430
  - No pause screen, or a pause that isn't bound to Escape.
431
+ - The OS arrow parked over the action for the whole session in a keyboard-driven
432
+ game (the cursor is either a gameplay tool or locked away — see the cursor
433
+ section).
402
434
  - A style brief whose fonts were never actually loaded (a system-stack display
403
435
  font at runtime).
404
436
  - A micro-element (reticle, cue, toast, damage number) left as default CSS
@@ -15,7 +15,7 @@ map, execution order, and acceptance gate.
15
15
 
16
16
  | Work needed | Load |
17
17
  | --- | --- |
18
- | shot composition, chase/side/orbit rigs, camera handoffs, projection ownership, pointer look, mouse-aimed action (shooter, FPS/first-person, sniper, turret, crosshair/reticle), mouse-look, floating origins | `$genex-threejs-camera-direction` |
18
+ | shot composition, chase/side/orbit rigs, camera handoffs, projection ownership, pointer look, mouse-aimed action (shooter, FPS/first-person, sniper, turret, crosshair/reticle), mouse-look, hand-rolled steering/pan/look input signs (screen-direction contract), floating origins | `$genex-threejs-camera-direction` |
19
19
  | on-foot player movement: walk/run/jump/crouch, third-person character, slopes, stairs, moving platforms, personal VRM animation, directional locomotion, transitions, action motion | `$genex-threejs-character-controller` |
20
20
  | a custom generated playable humanoid or Meshy animation coverage beyond UAL: reference-informed A-pose concepts, explicit candidate + 10k-remesh approvals, exact action IDs, same-rig adapter | `$genex-ai-character` + `$genex-threejs-character-controller` |
21
21
  | the player drives or flies something: cars, drones, vehicle physics, gearbox, enter/exit between character and vehicle | `$genex-threejs-vehicle-controllers` |
@@ -204,8 +204,16 @@ concept-driven — a richer first build beats a grey-box one.
204
204
  drag-orbit (`pointerLockAim: false`) only with a stated reason (e.g. a
205
205
  cursor-heavy UI core). **Never** — cursor-core games (click-to-move, tower
206
206
  defense, builder, card/puzzle), orbit showcases, touch-only; these MUST pass
207
- `pointerLockAim: false`. The mechanism and the full aim contract live in
208
- `$genex-threejs-camera-direction`.
207
+ `pointerLockAim: false`. Keyboard-only games (racer, platformer) lock too
208
+ the cursor is either a gameplay tool or locked away during play. The mechanism
209
+ and the full aim contract live in `$genex-threejs-camera-direction`.
210
+ - Input direction (always — every game that moves with keys or pointer):
211
+ D/ArrowRight must move or turn the player screen-RIGHT, mouse-right must turn
212
+ the view right, drag-pan axes share one convention. The screen-direction
213
+ contract and verified copy-paste bases live in
214
+ `$genex-threejs-camera-direction` — hand-rolled steering/pan/look math copies
215
+ one instead of deriving signs, and the smoke check's input-direction pass
216
+ verifies it.
209
217
  - Art direction follows THIS game's concept. The style examples inside skills
210
218
  are examples, not defaults — never default to neon/cyberpunk/synthwave (or any
211
219
  other single register) unless the concept calls for it.
@@ -222,7 +230,7 @@ concept-driven — a richer first build beats a grey-box one.
222
230
  - Use `$genex-threejs-visual-validation` before declaring graphics/procedural-system
223
231
  work done. **Game fast path:** for game tasks that loaded no procedural/visual-system
224
232
  skill, done = a screenshot plus an interaction smoke check (load the page, press each
225
- control, see the visible response) — don't run the full diagnostic gate — PLUS the
233
+ control, see the visible response in its labeled direction) — don't run the full diagnostic gate — PLUS the
226
234
  floors from the routing-map's acceptance gate: the generated sprite HUD wired in
227
235
  (not the CSS placeholder), pause on Escape, the branded loader with its key-art
228
236
  background, the brief's fonts actually loaded, the renderer baseline + at least one
@@ -180,7 +180,10 @@ Three.js release or branch, and do not blindly copy demo architecture.
180
180
  (`$genex-threejs-visual-validation` owns the capture discipline).
181
181
  7. Add camera direction when framing, controls, transitions, or scale perception
182
182
  affect play — or the game aims with the mouse (shooter/FPS/turret): the
183
- step-4 bucket decision executes here.
183
+ step-4 bucket decision executes here. And before ANY hand-rolled
184
+ steering/pan/look math: the screen-direction contract (D → screen-right,
185
+ mouse-right → view right, drag axes one convention) with verified copy-paste
186
+ bases lives there — signs are copied, never derived.
184
187
  8. Add procedural animation when object motion needs authored phases,
185
188
  convergence, looping, or deterministic timelines.
186
189
  9. Add shared fields before writing multiple independent noise layers.
@@ -250,7 +253,7 @@ module — a corrupted entry point costs more than the minutes saved.
250
253
 
251
254
  **Game fast path:** for a game task that loaded no procedural/visual-system skill,
252
255
  done = a screenshot plus an interaction smoke check (load the page, press each
253
- control, assert a visible response — `$genex-threejs-visual-validation` has the
256
+ control, assert a visible response in its labeled direction — `$genex-threejs-visual-validation` has the
254
257
  procedure), **plus the UI floor from `$genex-threejs-game-ui` (the generated
255
258
  sprite HUD wired in — not the CSS placeholder — pause on Escape, the branded
256
259
  loader with its key-art background, the brief's font pair actually loaded)
@@ -75,6 +75,10 @@ Pick what matches the game; most games need exactly one or two of these.
75
75
  lives on the stick; the static circle is fine for slower games. On the
76
76
  bundled character controller, pass it through instead — see
77
77
  `$genex-threejs-character-controller` (`joystick: { x: joy.x, y: joy.y }`).
78
+ Touch axes obey the same screen-direction contract as WASD and the mouse
79
+ (`$genex-threejs-camera-direction`): stick-right must move the player
80
+ screen-right, drag-right must turn the view right — a flipped feel is a sign
81
+ bug in the mapping, never a device quirk.
78
82
  - **Camera look → drag zone on the right half.** Default `DragZone()` is
79
83
  exactly that; per frame `const { dx, dy } = look.consumeDelta()` then apply
80
84
  to yaw/pitch with the same sensitivity scale as the mouse path. Games on the
@@ -72,9 +72,17 @@ everything twice.
72
72
  skill's "Self-testing a draft" section) — so you see the game, not the
73
73
  sign-in gate.
74
74
  2. Press each documented control once (keys, pointer); assert a **visible
75
- response** to every onethe player moves, the camera turns, the button
76
- fires. For an animated character, capture idle, walk, run, crouch-idle,
77
- crouch-move, and jump. Inspect shoulders, elbows, wrists, and hands as well
75
+ response in its labeled direction** this is the input-direction pass, and
76
+ it is part of THIS check, not an extra testing loop. Hold `KeyD`/ArrowRight
77
+ ~0.5s and screenshot-diff: the controlled thing moves or turns toward
78
+ screen-RIGHT (for a pan camera the viewport slides right — the terrain
79
+ streams LEFT); `KeyA`/ArrowLeft mirrors it. Keyboard synthesis needs no
80
+ pointer lock, so this works headless. For drag rigs, drag right AND drag up:
81
+ both axes must follow the one stated convention (grab-the-world or
82
+ move-the-camera — never one each). A response in the WRONG direction is a
83
+ fail, not a note: two shipped games passed "controls respond" while D
84
+ steered screen-left. For an animated character, capture idle, walk, run,
85
+ crouch-idle, crouch-move, and jump. Inspect shoulders, elbows, wrists, and hands as well
78
86
  as the feet: a fully bound non-T-pose can still be stylistically broken.
79
87
  Reject shrugging palms-up poses, permanently raised elbows, or a gait whose
80
88
  upper-body style contradicts the requested character. “No T-pose” is not
@@ -104,16 +112,19 @@ everything twice.
104
112
  floating above detached wheels means the model was box-fit against the
105
113
  preset's wheelbase (the vehicle skill's "Custom generated bodies" rules
106
114
  fix it).
107
- 6. Aim games get one extra pass (MANDATORY bucket — first-person or mouse-aimed):
108
- state which pointer bucket the game chose; click the canvas and assert the
109
- pointer locks (cursor gone, mouse-RIGHT turns the view RIGHT check the axis,
110
- not just that it moves); press Esc and assert the "click to aim/resume" cue
111
- appears. The cue should be the bundled `createAimCue` helper (or an equivalent
115
+ 6. The cursor pass every game, per the lock-or-tool rule: during play the OS
116
+ cursor is either the gameplay tool (cursor-core confirm the explicit
117
+ `pointerLockAim: false` opt-out, cursor stays) or it is LOCKED away; a
118
+ non-cursor-core game with the arrow parked over the action is a defect, even
119
+ a keyboard-only racer. Aim games additionally: click the canvas and assert
120
+ the pointer locks (cursor gone, mouse-RIGHT turns the view RIGHT and
121
+ mouse-UP looks UP — name BOTH axes; yaw-only evidence has let inverted pitch
122
+ ship); press Esc and assert the "click to aim/resume" cue appears. The cue
123
+ should be the bundled `createAimCue` helper (or an equivalent
112
124
  `onAimChange`-driven overlay) — a MANDATORY-bucket game with no unlocked cue
113
- fails. Also confirm a cursor-core game opted OUT (`pointerLockAim: false`) so it
114
- isn't grabbing the cursor. Headless caveat: `requestPointerLock` throws in
115
- headless Chromium assert the wiring and the unlocked cue in a screenshot, and
116
- say plainly that the lock itself needs one manual click.
125
+ fails. Headless caveat: `requestPointerLock` throws in headless Chromium
126
+ assert the wiring and the unlocked cue in a screenshot, and say plainly that
127
+ the lock itself needs one manual click (do the both-axes look check there).
117
128
  7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
118
129
  once, in the same browser you already have open.
119
130
 
@@ -279,6 +290,12 @@ visual-system work — the sequence above.
279
290
 
280
291
  - a MANDATORY-bucket aim game never requests pointer lock, or locks with no
281
292
  visible unlocked cue;
293
+ - a movement key or look axis whose on-screen direction contradicts its label
294
+ (D turning the vehicle screen-left, mouse-up looking down with no invert
295
+ option);
296
+ - drag-pan axes that mix conventions (one axis grab-the-world, the other
297
+ move-the-camera);
298
+ - a non-cursor-core game that leaves the OS cursor visible during play;
282
299
  - approval relies on a single frame;
283
300
  - post-processing cannot be disabled per pass;
284
301
  - random seeds are not reproducible;