@maccesar/titools 5.2.0 → 5.2.2
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/CONTRIBUTING.md +82 -0
- package/README.md +83 -72
- package/agents/ti-pro.md +1 -1
- package/commands/ti-audit.md +2 -2
- package/docs/examples.md +91 -0
- package/docs/knowledge-index.md +57 -0
- package/lib/commands/uninstall.js +19 -16
- package/lib/commands/update.js +1 -1
- package/lib/downloader.js +1 -1
- package/lib/utils.js +2 -0
- package/package.json +4 -2
- package/skills/purgetss/references/multi-density-images.md +1 -1
- package/skills/ti-admob-unified/SKILL.md +21 -7
- package/skills/ti-admob-unified/assets/example/app.js +229 -25
- package/skills/ti-admob-unified/assets/example/lib/ads.js +30 -2
- package/skills/ti-admob-unified/assets/example/library-usage.js +33 -1
- package/skills/ti-admob-unified/references/api-reference.md +39 -16
- package/skills/ti-admob-unified/references/banners.md +9 -9
- package/skills/ti-admob-unified/references/contributing.md +14 -9
- package/skills/ti-admob-unified/references/dependencies.md +31 -25
- package/skills/ti-admob-unified/references/fullscreen.md +43 -28
- package/skills/ti-admob-unified/references/game-monetization.md +138 -7
- package/skills/ti-admob-unified/references/getting-started.md +75 -31
- package/skills/ti-admob-unified/references/index.md +5 -2
- package/skills/ti-admob-unified/references/integration.md +8 -6
- package/skills/ti-admob-unified/references/library.md +41 -10
- package/skills/ti-admob-unified/references/mediation-parity.md +17 -0
- package/skills/ti-admob-unified/references/mediation.md +22 -9
- package/skills/ti-admob-unified/references/migration.md +11 -7
- package/skills/ti-admob-unified/references/module-changelog.md +41 -11
- package/skills/ti-admob-unified/references/privacy.md +35 -8
- package/skills/ti-admob-unified/references/production.md +67 -0
- package/skills/ti-admob-unified/references/rewarded-interstitial.md +101 -0
- package/skills/ti-admob-unified/references/roadmap.md +28 -31
- package/skills/ti-admob-unified/references/source-notes.md +51 -4
- package/skills/ti-admob-unified/references/source-snapshot.json +249 -45
- package/skills/ti-admob-unified/references/third-party-notices.md +4 -4
- package/skills/ti-game/SKILL.md +23 -6
- package/skills/ti-game/references/api.md +34 -30
- package/skills/ti-game/references/debugging-performance.md +18 -9
- package/skills/ti-game/references/examples.md +103 -20
- package/skills/ti-game/references/input.md +101 -0
- package/skills/ti-game/references/patterns.md +39 -15
- package/skills/ti-game/references/physics.md +80 -52
- package/skills/ti-game/references/project-setup.md +118 -56
- package/skills/ti-game/references/recipes-action.md +107 -8
- package/skills/ti-game/references/recipes-adventure-effects.md +41 -9
- package/skills/ti-game/references/roadmap.md +10 -3
- package/skills/ti-game/references/source-notes.md +81 -0
- package/skills/ti-game/references/tilemaps.md +12 -4
- package/skills/ti-synthengine/SKILL.md +81 -95
- package/skills/ti-synthengine/assets/example/README.md +84 -0
- package/skills/ti-synthengine/assets/example/app.js +1178 -0
- package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +83 -0
- package/skills/ti-synthengine/assets/example/lib/sequenceScore.js +25 -0
- package/skills/ti-synthengine/references/advanced-examples.md +174 -0
- package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
- package/skills/ti-synthengine/references/android-sim80-correction.json +162 -0
- package/skills/ti-synthengine/references/api.md +434 -380
- package/skills/ti-synthengine/references/audio-engine.md +157 -230
- package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
- package/skills/ti-synthengine/references/examples.md +123 -253
- package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-physical-validation.json +1918 -0
- package/skills/ti-synthengine/references/guide.md +188 -156
- package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
- package/skills/ti-synthengine/references/performance-review.json +1146 -0
- package/skills/ti-synthengine/references/physical-validation.json +1457 -0
- package/skills/ti-synthengine/references/recipes-interaction.md +549 -0
- package/skills/ti-synthengine/references/recipes.md +423 -599
- package/skills/ti-synthengine/references/recommendations.md +63 -113
- package/skills/ti-synthengine/references/sound-design.md +134 -258
- package/skills/ti-synthengine/references/source-notes.md +93 -105
- package/skills/ti-synthengine/references/source-snapshot.json +379 -32
- package/skills/ti-synthengine/references/stats-performance.md +64 -20
- package/skills/ti-synthengine/references/studio-validation.json +6202 -0
- package/skills/ti-synthengine/references/troubleshooting.md +109 -121
- package/skills/ti-synthengine/references/unified-bypass-performance.json +22425 -0
- package/skills/ti-synthengine/references/unified-validation.json +314 -0
- package/AGENTS-VERCEL-RESEARCH.md +0 -175
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# ti.game API reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Portable JS surface reviewed against `m1ga/ti.game` main at `88e181cb85526d8146d0be6e09b2b90c2551baad` and the documentation refresh in `macCesar/ti.game@2bf5f828a3bb8028eed20d6ba27b97dd3aa9a753`, 2026-10-23. Android uses OpenGL ES 2.0; Apple uses Metal on iPhone, iPad and Mac Catalyst. Both manifests declare `1.0.0` and minimum Titanium SDK `13.4.0`; this source review does not establish the version or behavior of an installed artifact. Check the artifact's source commit, especially nightly builds sharing a version. Feature-detect when the artifact is unknown: methods are safer probes than unknown property writes retained on a Kroll proxy.
|
|
4
4
|
|
|
5
|
-
Properties are live unless their table says creation-time or read-only. Every writable property can be passed to its `create*` factory. `
|
|
5
|
+
Properties are live unless their table says creation-time or read-only. Every writable property can be passed to its `create*` factory. Exceptions for portable code: `animations` must be supplied at Sprite creation, and TileLayer `legend`, `firstGid`, `cols`, `rows` must be supplied at TileLayer creation. All durations crossing the JS boundary are **milliseconds**. For keyboard, mouse, action maps and focus, read [input.md](input.md).
|
|
6
6
|
|
|
7
7
|
<!-- TOC-START -->
|
|
8
8
|
## Contents
|
|
@@ -100,10 +100,13 @@ A normal Titanium view — add it to a window or any container, size it with the
|
|
|
100
100
|
| `remove(object)` | method | — | Remove one object from the scene — **and, recursively, every sprite attached to it** (see [Attachment](#attachment)) |
|
|
101
101
|
| `removeAllSprites()` | method | — | Clears Sprite/Text objects and their attachments. It does **not** remove emitters, ropes, or tile layers; track and remove those separately |
|
|
102
102
|
| `pause()` / `resume()` | method | — | Render loop control. The activity/app lifecycle already pauses and resumes automatically |
|
|
103
|
-
| `backgroundColor` | string | — |
|
|
103
|
+
| `backgroundColor` | string | — | Native scene clear color |
|
|
104
104
|
| `maxFps` | int | `0` | Frame cap. `60` stops 120 Hz ProMotion displays from doubling render work; `0` = display refresh rate |
|
|
105
|
-
| `timeScale` | float | `1` | Multiplies the scene dt for physics, sheet animations, tweens, particles and camera. `0.5` = slow motion; at `0` rendering/touch continue
|
|
105
|
+
| `timeScale` | float | `1` | Multiplies the scene dt for physics, sheet animations, tweens, particles and camera. `0.5` = slow motion; at `0` rendering/touch continue while rope nodes, their history, constraints and tether response freeze. New rope geometry can still initialize for drawing; see [physics.md](physics.md#verlet-ropes-and-tethers) |
|
|
106
106
|
| `surfaceWidth` / `surfaceHeight` | int | — | Read-only surface size in pixels |
|
|
107
|
+
| `inputMap` | dict | `{}` | Contexts → named actions → binding arrays. Invalid maps throw without replacing the current map or active state; see [input.md](input.md) |
|
|
108
|
+
| `inputContext` | string / null | `'gameplay'` | Active action context; `null` disables actions. A changed context releases active actions and requires a fresh press |
|
|
109
|
+
| `focusInput()` | method | — | Request canvas keyboard focus on the UI thread; does not open the software keyboard or capture keys globally |
|
|
107
110
|
| `cameraX` / `cameraY` | float | `0` | World-space offset of the view |
|
|
108
111
|
| `cameraScale` | float | `1` | Zoom, anchored on the view center |
|
|
109
112
|
| `cameraBounds` | dict / null | `null` | `{ minX, minY, maxX, maxY }` world rect the visible area is clamped into |
|
|
@@ -111,7 +114,7 @@ A normal Titanium view — add it to a window or any container, size it with the
|
|
|
111
114
|
| `follow(sprite, options)` | method | — | Native dead-zone follow, see below |
|
|
112
115
|
| `stopFollow()` | method | — | Stop following; the camera stays where it is |
|
|
113
116
|
| `shake({ strength, duration })` | method | `12` px, `400` ms | Detuned-sine rumble on the projection only — follow, bounds and touch mapping are unaffected |
|
|
114
|
-
| `raycast(x0, y0, x1, y1, groups)` | method | — | One-shot nearest-hit query along the segment against **visible, non-`screenFixed`** sprites carrying a `collisionGroup` in `groups` (omit for any tagged sprite). Returns `null` for a clear ray, else `{ x, y, distance, group, sprite, normal: { x, y } }`.
|
|
117
|
+
| `raycast(x0, y0, x1, y1, groups)` | method | — | One-shot nearest-hit query along the segment against **visible, non-`screenFixed`** sprites carrying a `collisionGroup` in `groups` (omit for any tagged sprite). Returns `null` for a clear ray, else `{ x, y, distance, group, sprite, normal: { x, y } }`. Plain rect hitboxes use their AABB; circle and `rotatedRect` hitboxes use their actual shapes; a ray starting inside a hitbox reports it at distance 0 |
|
|
115
118
|
| `findPath(from, to, options)` | method | — | Grid A\* from `from` to `to` (`{ x, y }` world points) around visible, non-`screenFixed` sprites and fully solid TileLayer cells carrying a `collisionGroup` in `options.groups` (omit for any tagged obstacle). Returns `{ x, y }` waypoints ready for `sprite.followPath()`, or `null`. Options below |
|
|
116
119
|
| `after(ms, callback)` | method | — | Runs `callback` once after `ms` of **game time** — the delay stretches under `timeScale` slow motion, freezes at `0` and pauses with the render loop, unlike `setTimeout`. Returns an int id. The callback receives `{ id }` |
|
|
117
120
|
| `every(ms, callback)` | method | — | Like `after()`, repeating until cancelled. Fires at most once per frame, and restarts its interval after a stall instead of bursting to catch up |
|
|
@@ -127,6 +130,8 @@ A normal Titanium view — add it to a window or any container, size it with the
|
|
|
127
130
|
|
|
128
131
|
Events: `press`, `tap`, `release` — fired for **every** touch anywhere on the view, with payload `x`, `y` in scene coordinates (tap-anywhere controls, flappy-style). `resize` carries the real surface `width`, `height` in pixels. `performance` carries a one-second telemetry snapshot only while a listener exists; see [debugging-performance.md](debugging-performance.md).
|
|
129
132
|
|
|
133
|
+
Keyboard `keydown` / `keyup` carry `code`, `character`, and `modifiers: { shift, control, alt, meta }`. Action `actiondown` / `actionup` carry `action` and `context`; cancellation may release actions without raw `keyup`. See [input.md](input.md).
|
|
134
|
+
|
|
130
135
|
Plus `timer` with payload `id`, fired only for `after()` / `every()` calls made **without** a callback — pass a callback or listen for the event, not both.
|
|
131
136
|
|
|
132
137
|
Kroll never runs a `@Kroll.setProperty` setter for a key passed to `createGameView()`: each one needs its own branch in the native creation handler. `cameraX`, `cameraY`, `cameraScale` and `cameraBounds` were silently dropped at creation before `3bea2f4` and are honored from it on. Assigning them after creation always worked and still does — prefer that when the installed artifact is unknown.
|
|
@@ -192,9 +197,7 @@ On Android the buttons are captured at the activity window: the game view needs
|
|
|
192
197
|
|
|
193
198
|
`platformer.js` runs on either the on-screen buttons or a pad.
|
|
194
199
|
|
|
195
|
-
|
|
196
|
-
retained iOS GameViews: the pinned controller adapter overwrites a shared
|
|
197
|
-
GCController callback. See [lifecycle and input ownership](project-setup.md#retained-ios-scenes-and-multiple-gameviews).
|
|
200
|
+
Every live GameView receives controller input independently, including retained views under a modal scene. Closing one releases its input and unregisters only that receiver. Apple receivers share the GameController profile callback with registration, dispatch and cleanup on the main queue. Route input to the active screen in app code; see [lifecycle and input ownership](project-setup.md#retained-ios-scenes-and-multiple-gameviews).
|
|
198
201
|
|
|
199
202
|
## SpriteSheet
|
|
200
203
|
|
|
@@ -205,17 +208,17 @@ Create one sheet per texture and share it across every sprite that uses it — s
|
|
|
205
208
|
| `image` | — | Path to the texture, relative to the resources root (`'assets/hero.png'`) |
|
|
206
209
|
| `frameWidth` / `frameHeight` | — | Grid mode: cell size in source pixels. Frames are numbered left-to-right, top-to-bottom, starting at 0 |
|
|
207
210
|
| `atlas` | — | TexturePacker JSON (hash or array format) instead of a grid |
|
|
208
|
-
| `smoothing` | `true` | `false` =
|
|
209
|
-
| `repeat` | `false` |
|
|
211
|
+
| `smoothing` | `true` | `false` = nearest-neighbor filtering, the pixel-art setting. On a **grid** sheet, `true` also insets the frame UVs by half a texel so magnified edges cannot sample the neighbouring frame — **both** edges of any axis that has more than one frame, since 2026-08-24. An axis with a single frame keeps the exact 0..1 range, which is what `tileRepeat` needs to wrap. Atlas frames come from the JSON untouched |
|
|
212
|
+
| `repeat` | `false` | Texture repeat, required by sprites using `tileRepeat`. Android OpenGL ES 2.0 requires power-of-two texture dimensions |
|
|
210
213
|
|
|
211
214
|
| Member | Notes |
|
|
212
215
|
| --- | --- |
|
|
213
216
|
| `frameCount` | Number of frames. `0` until the texture has loaded, for grid sheets |
|
|
214
217
|
| `frameNames` | Sorted frame names — atlas sheets only |
|
|
215
218
|
| `frameIndex(name)` | Index for an atlas frame name, `-1` if unknown |
|
|
216
|
-
| `unload()` |
|
|
219
|
+
| `unload()` | Permanently disposes the GPU texture on the render thread. Sprites still using the sheet stop drawing. For level streaming: unload the finished level's atlases instead of accumulating GPU memory. Since 2026-08-23 |
|
|
217
220
|
|
|
218
|
-
Textures upload to the GPU lazily on first use, from the render thread, and are re-created automatically after an EGL context loss. Releasing the proxy unloads its texture too, so a sheet that goes out of scope does not strand GPU memory.
|
|
221
|
+
Textures upload to the GPU lazily on first use, from the render thread, and are re-created automatically after an Android EGL context loss. Apple resources are Metal textures; do not apply EGL-specific lifecycle assumptions to Apple views. Releasing the proxy unloads its texture too, so a sheet that goes out of scope does not strand GPU memory.
|
|
219
222
|
|
|
220
223
|
## Sprite
|
|
221
224
|
|
|
@@ -244,12 +247,12 @@ Textures upload to the GPU lazily on first use, from the render thread, and are
|
|
|
244
247
|
| Property | Default | Notes |
|
|
245
248
|
| --- | --- | --- |
|
|
246
249
|
| `sheet` | — | The SpriteSheet |
|
|
247
|
-
| `frame` | `0` | Current frame index; writing it stops
|
|
248
|
-
| `animations` | — |
|
|
250
|
+
| `frame` | `0` | Current frame index; writing it stops the running sheet animation and its queued chain, then shows that frame |
|
|
251
|
+
| `animations` | — | Creation-only for portable code; iOS also accepts later assignment, Android has no live setter. Definitions: `{ walk: { frames: [1, 2], fps: 6, loop: true } }` |
|
|
249
252
|
| `animation` | — | Read-only name of the current animation. **Keeps its value after `stop()`** — track "is walking" yourself if you need it |
|
|
250
253
|
| `tileRepeat` | `false` | `true` / `'x'` / `'y'` — tile the frame at native size instead of stretching. Needs `repeat: true` on the sheet and a frame spanning the whole texture |
|
|
251
254
|
|
|
252
|
-
An animation definition takes `frames` (array of indices), `fps`, `loop`, and an optional `frame`
|
|
255
|
+
An animation definition takes `frames` (array of indices), `fps`, `loop`, and, on Android only, an optional completion `frame` (default: hold the last animation frame). iOS ignores that definition key. Both platforms support a completion `frame` in `animate()`.
|
|
253
256
|
|
|
254
257
|
Methods: `play(name)` starts an animation, `stop()` halts it on the current frame.
|
|
255
258
|
|
|
@@ -275,7 +278,7 @@ Hit-testing runs against the transformed shape (rotation and scale included), to
|
|
|
275
278
|
| `velocityX`, `velocityY` | `0` | px/s |
|
|
276
279
|
| `gravity` | `0` | px/s², added to `velocityY`. Per sprite — there is no global gravity |
|
|
277
280
|
| `gravityX` | `0` | px/s², added to `velocityX`. The **sibling** of `gravity`, not half of a vector: `gravity` keeps its exact vertical meaning. Wind, a conveyor, or a top-down game whose "down" is sideways (`gravity: 0`, `gravityX` non-zero). Since 2026-08-27 |
|
|
278
|
-
| `linearDamping` | `0` | Coefficient in 1/s: each step retains `max(0, 1−linearDamping·dt)` of both velocity axes, after thrust/gravity and before position.
|
|
281
|
+
| `linearDamping` | `0` | Coefficient in 1/s: each step retains `max(0, 1−linearDamping·dt)` of both velocity axes, after thrust/gravity and before position. The **4 px/s** rest cutoff stops coasting and blocked residual motion, but effective thrust, gravity or powered car input can build velocity from rest. The post-contact decision excludes blocked motion and cancelled forces; damping still attenuates acceleration and depends on dt. ~`0.6` is used for pool felt, but damping acts even without contact. Negative values clamp to0. See [physics.md](physics.md#thrust-inertia-and-linear-damping) |
|
|
279
282
|
| `maxSpeed` | `500` | px/s cap applied **only** to `thrust` acceleration and the `carMode` model. Writing `velocityX`/`velocityY` directly is never clamped |
|
|
280
283
|
| `angularVelocity` | `0` | deg/s |
|
|
281
284
|
| `thrust` | `0` | px/s² along the current heading (Newtonian flight) |
|
|
@@ -325,7 +328,7 @@ Two consequences that cost an afternoon each if you meet them by surprise:
|
|
|
325
328
|
| `hitboxScale` | `1` | Shrinks the collision box **around the anchor** — which is what makes `anchor` the other half of the tuning: `anchor: 'bottom'` with `hitboxScaleY: '55%'` covers the lower half without lifting the box off the floor. Also takes `'80%'` |
|
|
326
329
|
| `hitboxScaleX`, `hitboxScaleY` | `1` | Per-axis corrections **multiplied on top of** `hitboxScale`, for art that fills its frame by a different fraction on each axis. A 20×44 drawing in a 32×48 frame needs `0.62` wide and `0.92` tall — no single `hitboxScale` describes it. Ignored by circle hitboxes. Since 2026-08-23 |
|
|
327
330
|
| `hitboxShape` | `'rect'` | `'circle'` — radius = half the smaller drawn side × `hitboxScale` (the per-axis scales are skipped: a circle has no axes). Circles resolve against solids along the contact normal (corner bounces) and get a round touch area. `'rotatedRect'` keeps the collision rect **turned with the sprite** (an OBB) instead of re-boxing it square to the screen: a plain `'rect'` at 45° is re-boxed around its turned corners and comes out 41% wider with a flat top that is not there. Only matters once `rotation` is non-zero, and it does honour `hitboxScaleX`/`hitboxScaleY`, unlike a circle. Covers solids, `collidesWith` overlap, `raycast`, the swept pass and the debug overlay. Since 2026-08-27 |
|
|
328
|
-
| `swept` | `false` | Test this sprite's movement as a **path
|
|
331
|
+
| `swept` | `false` | Test this sprite's movement as a **path**, not just at the end position, so a fast mover cannot tunnel between frames. Applies to both `collidesWith` events and `solidWith` blocking (the sprite is clamped to the impact point, then resolved by the normal static pass). Two **circle** hitboxes now sweep as circles (the Minkowski sum of two circles is a circle of radius r1+r2); trigger sweeps for circle/rectangle pairs use a rounded box (including turned boxes), while rectangle pairs use AABBs. The `solidWith` blocking sweep is separate: circle pairs use circles; circle-versus-rect blocking keeps its own expanded-box/rounded-OBB path. Do not infer identical trigger and blocking geometry. Set it on the *mover* — the bullet, not the wall. The sweep skips solids that are not `solidMode: 'block'` |
|
|
329
332
|
| `debug` | `false` | Draw this sprite's shapes: green = collision AABB, blue = touch bounds, orange dot = anchor |
|
|
330
333
|
|
|
331
334
|
### Car (`carMode`)
|
|
@@ -373,7 +376,7 @@ Every sprite gets its own phase, and the wobble unwinds exactly when disabled. T
|
|
|
373
376
|
| --- | --- |
|
|
374
377
|
| `play(name, options)` | Start a named animation; returns `false` for an unknown name. `options.then` (a name or an array of names) chains natively — each queued animation starts as the previous non-looping one finishes, and `animationcomplete` still fires per step. A looping animation ends the chain |
|
|
375
378
|
| `stop()` | Stop the animation on the current frame; also drops any queued `then` chain |
|
|
376
|
-
| `followPath(points, options)` | Walk the sprite along `points` — `{ x, y }` objects or `[x, y]` pairs, **at least two
|
|
379
|
+
| `followPath(points, options)` | Walk the sprite along `points` — `{ x, y }` objects or `[x, y]` pairs, **at least two**; fewer logs a warning and clears the path. `options`: `speed` px/s (default `100`), `loop` (closed circuit), `rotate` (face along the path, 0 = up), `smoothing` (corner radius in px, precomputed once). Fires `pathcomplete` at the end of a non-looping run and clears itself. `followPath(null)` stops in place |
|
|
377
380
|
| `animate(options)` | Native tween, fires `complete` |
|
|
378
381
|
| `clearTweens()` | Cancel tweens in progress — call before starting a replacement tween |
|
|
379
382
|
| `flash(color, duration)` | Fills the sprite's silhouette with `color` (default white) and fades it out over `duration` ms (default 150). The classic damage/invincibility flash that a multiplicative `tintColor` cannot do |
|
|
@@ -435,7 +438,7 @@ The rest of the contract, all read from the engine:
|
|
|
435
438
|
|
|
436
439
|
## Font
|
|
437
440
|
|
|
438
|
-
A glyph atlas for text sprites. Create it once, outside `resize`, like a SpriteSheet — it holds a
|
|
441
|
+
A glyph atlas for text sprites. Create it once, outside `resize`, like a SpriteSheet — it holds a GPU texture.
|
|
439
442
|
|
|
440
443
|
```javascript
|
|
441
444
|
// 1. Built-in: nothing to ship. A 9x15 pixel font embedded in the module.
|
|
@@ -460,6 +463,8 @@ const mono = Game.createFont({ image: 'assets/mono.png', charWidth: 9, charHeigh
|
|
|
460
463
|
| `lineHeight` | — | Read-only: the font's natural line height in px |
|
|
461
464
|
| `unload()` | Method, not an option: frees the glyph texture exactly like `SpriteSheet.unload()`, and just as permanently. Since 2026-08-23 |
|
|
462
465
|
|
|
466
|
+
The built-in font is Press Start 2P with 9×15 cells: ASCII 32..126 plus `ÁÉÍÓÚÜÑáéíóúüñ¿¡ÄÖäößËÏëïÈèÊêÀà`. It accepts these precomposed characters, not combining accents; `ẞ` is absent, so uppercase German text uses `SS`. Custom grid fonts still default to ASCII only. The source TTF and SIL Open Font License are in `tools/default-font/`; regenerate both embedded images with `uv run --with pillow --with fonttools python tools/build_default_font.py`.
|
|
467
|
+
|
|
463
468
|
A descriptor that fails to parse falls back to the built-in font and logs the error — the text still renders, in the wrong face, which is the symptom to recognize.
|
|
464
469
|
|
|
465
470
|
## Text
|
|
@@ -520,12 +525,12 @@ music.play();
|
|
|
520
525
|
|
|
521
526
|
| Option | Default | Notes |
|
|
522
527
|
| --- | --- | --- |
|
|
523
|
-
| `url` | required |
|
|
528
|
+
| `url` | required | Creation-only app resource or file path |
|
|
524
529
|
| `volume` | `1` | 0..1, live |
|
|
525
530
|
| `loop` | `false` | Live |
|
|
526
531
|
| `music` | `false` | Chooses the backend — **set at creation, not changeable later** |
|
|
527
532
|
|
|
528
|
-
Effect mode (the default) is built for low latency: a shared `SoundPool` on Android, a small pool of preloaded players on iOS. Call `play()` from any handler and repeated plays overlap instead of cutting each other off. `music: true` picks the streaming backend (`MediaPlayer` / `AVAudioPlayer`) for longer tracks; music pauses when the app backgrounds and resumes with it.
|
|
533
|
+
Effect mode (the default) is built for low latency: a shared `SoundPool` on Android, a small pool of preloaded players on iOS. Call `play()` from any handler and repeated plays overlap instead of cutting each other off; a play requested during loading is queued. `music: true` picks the streaming backend (`MediaPlayer` / `AVAudioPlayer`) for longer tracks; music pauses when the app backgrounds and resumes with it.
|
|
529
534
|
|
|
530
535
|
Methods: `play()`, `pause()` (resumes where it stopped), `stop()` (rewinds to the beginning). Formats: WAV, MP3, OGG on Android; WAV, MP3, M4A on iOS.
|
|
531
536
|
|
|
@@ -550,7 +555,7 @@ Add and remove like a sprite: `gameView.add(emitter)` / `remove(emitter)`. Spawn
|
|
|
550
555
|
| `size` | `0` | Base particle width in px; `0` = frame size |
|
|
551
556
|
| `startScale` / `endScale` | `1` / `1` | Interpolated over `lifetime` |
|
|
552
557
|
| `startOpacity` / `endOpacity` | `1` / `0` | Interpolated over `lifetime` |
|
|
553
|
-
| `tint` | white | Tint white particle art at runtime — one tiny texture covers every color in the game |
|
|
558
|
+
| `tint` | white | Write-only. Tint white particle art at runtime — one tiny texture covers every color in the game |
|
|
554
559
|
| `blend` | `'normal'` | `'add'` for fire, sparks and magic; `'multiply'` for smoke and dust that darken what they cross; `'screen'` for soft haze |
|
|
555
560
|
| `maxParticles` | `200` | Pool size, hard cap 1000 |
|
|
556
561
|
|
|
@@ -577,9 +582,8 @@ A native Verlet chain: integration and distance constraints run in the game loop
|
|
|
577
582
|
| `visible` | `true` | |
|
|
578
583
|
| `endX`, `endY` | — | Read-only live position of the loose end (grappling-hook tips) |
|
|
579
584
|
|
|
580
|
-
Per-step retention is not a per-second rate.
|
|
581
|
-
|
|
582
|
-
see [Verlet equations and pause limits](physics.md#verlet-ropes-and-tethers).
|
|
585
|
+
Per-step retention is not a per-second rate. At `dt<=0`, node positions, previous positions, constraint relaxation and tether response stay frozen. New geometry still initializes so a newly created rope can draw while paused;
|
|
586
|
+
see [Verlet equations and pause behavior](physics.md#verlet-ropes-and-tethers).
|
|
583
587
|
|
|
584
588
|
The tether yields at the end no finger owns: with a fixed head anchor the tail sprite is simply leashed, but with sprites on both ends you can drag either one and the other is towed once the rope goes taut.
|
|
585
589
|
|
|
@@ -589,7 +593,7 @@ One native grid replaces a sprite per tile. Only cells inside the camera are dra
|
|
|
589
593
|
|
|
590
594
|
| Group | Members | Notes |
|
|
591
595
|
| --- | --- | --- |
|
|
592
|
-
| Grid input | `data` | Nested numeric rows, string rows decoded through `legend`, or flat row-major ids sized by `cols`/`rows`. Live on both platforms |
|
|
596
|
+
| Grid input | `data` | Live write-only input. Nested numeric rows, string rows decoded through `legend`, or flat row-major ids sized by `cols`/`rows`. Live on both platforms |
|
|
593
597
|
| Creation-time grid configuration | `legend`, `firstGid`, `cols`, `rows` | Set in `createTileLayer()` for portability. `cols`/`rows` read back from the parsed grid; Android has no live setters for the four configuration inputs |
|
|
594
598
|
| Derived size | `width`, `height` | Read-only world dimensions |
|
|
595
599
|
| Placement | `x`, `y`, `tileWidth`, `tileHeight`, `zIndex`, `scrollFactor` | Tile sizes default to the sheet frame dimensions |
|
|
@@ -603,7 +607,7 @@ Tiled data uses `firstGid`: gid 0 becomes empty when an offset is active, and fl
|
|
|
603
607
|
| `getTile(col, row)` | Frame id, `-1` when empty or outside |
|
|
604
608
|
| `setTile(col, row, id)` | Replaces art and recalculates its flag from `solid`/`oneWay` |
|
|
605
609
|
| `isBlocked(col, row)` | Fully solid status; one-way cells answer `false` |
|
|
606
|
-
| `setBlocked(col, row, bool)` | Per-cell full-solid override without changing art |
|
|
610
|
+
| `setBlocked(col, row, bool)` | Per-cell full-solid override without changing art, until `data`, `solid` or `oneWay` changes |
|
|
607
611
|
| `tileAt(x, y)` | World point → `{ col, row, tile, solid, x, y }`, or `null` outside |
|
|
608
612
|
| `cellAt(col, row)` | Cell → the same structure; `x`/`y` are the center |
|
|
609
613
|
|
|
@@ -613,7 +617,7 @@ Movers list the layer's `collisionGroup` in `solidWith`. Rect, circle, swept, re
|
|
|
613
617
|
|
|
614
618
|
Only these events exist. Nothing fires per frame, and events are only fired if a listener is registered.
|
|
615
619
|
|
|
616
|
-
- **GameView**: `press`, `tap`, `release`, `resize`, `timer`, `performance`, `buttondown`, `buttonup`, `stick`, `trigger`, `gamepadconnected`, `gamepaddisconnected`
|
|
620
|
+
- **GameView**: `press`, `tap`, `release`, `resize`, `timer`, `performance`, `buttondown`, `buttonup`, `stick`, `trigger`, `gamepadconnected`, `gamepaddisconnected`, `keydown`, `keyup`, `actiondown`, `actionup`
|
|
617
621
|
- **Sprite** (and therefore **Text**): `press`, `release`, `tap`, `dragstart`, `drag`, `dragend`, `pinch`, `rotate`, `animationcomplete`, `complete`, `pathcomplete`, `collision`, `collisionend`, `land`, `wallhit`, `solidimpact`
|
|
618
622
|
- **SpriteSheet, Font, Sound, Emitter, Rope, TileLayer**: none
|
|
619
623
|
|
|
@@ -631,12 +635,12 @@ There is `collision` (enter) and `collisionend` (exit), but deliberately **no**
|
|
|
631
635
|
- **`music` is chosen at creation.** Creating an effect sound and flipping `music` later does not switch backends.
|
|
632
636
|
- **`collision` fires once per overlap-enter**, re-arming only after the shapes separate. A sprite resting inside a trigger does not re-fire — `collisionend` tells you when it left.
|
|
633
637
|
- **Pooling a sprite with `visible = false` fires `collisionend`** on whoever was touching it. That is usually what you want (a plate whose ball was despawned closes its door), but a handler that assumes separation means "the player walked away" will misfire.
|
|
634
|
-
- **The built-in
|
|
638
|
+
- **The built-in font includes a specific accented Latin repertoire, not arbitrary Unicode.** Precomposed Spanish `ñ` and the listed accented characters work; combining accents, em dashes and unlisted glyphs need a custom BMFont/grid atlas. Custom grid fonts default to ASCII 32..126 unless `characters` is supplied.
|
|
635
639
|
- **A bouncy floor switches `onGround` off.** In every resolver the grounding and the `land` event live in the branch taken when the bounce is *damped away*; a contact whose rebound would exceed 40 px/s reflects and grounds nothing. That was always true of a bouncy rider, but `restitution` now mixes as `max(mover, solid)`, so giving a *floor* `restitution: 0.5` silently stops every rider that lands hard on it from ever reporting `onGround` — and a jump gated on `onGround` stops working on that one platform.
|
|
636
640
|
- **A two-sided `restitution` cannot be opted out of.** The mix is `max`, not a product or an average, so the springier surface always wins: a deliberately dead `restitution: 0` crate still bounces off a floor set to `0.5`. If one rider must not bounce, the bounce has to live on the riders, not on the surface.
|
|
637
641
|
- **A resting body is allowed to sink half a pixel — in some resolvers.** Separation is skipped when the overlap is under 0.5 px, because shoving a gravity-loaded body back out sixty times a second is what makes a settled pile tremble; the closing velocity is still cancelled, so the sink cannot grow past it. It applies to the circle path, the turned-rect path and `'push'` pairs. Plain rect-against-rect still separates in full. Do not derive an exact contact position from `y` after a landing.
|
|
638
642
|
- **`linearDamping` is not surface friction.** Nothing checks whether the sprite is touching anything: it bleeds speed on both axes every frame, in the air as much as on the felt, and along a slope as much as across it. It is right for a pool table and wrong for a hill, which is why upstream still lists contact-only friction as unbuilt. A projectile that must keep its arc wants `0`.
|
|
639
|
-
- **`linearDamping`
|
|
643
|
+
- **`linearDamping` stops slow coasting.** Under 4 px/s it stops unpowered drift; an effective acceleration can now build speed from rest. Forces cancelled by opposing forces or a solid do not keep residual tangential coasting alive. This rest policy does not make Euler damping independent of frame rate.
|
|
640
644
|
- **A swept sprite is not contained and not pushed.** The sweep only considers solids in `solidMode: 'block'` — a `'contain'` boundary would stop the ball against its *outside*, and a `'push'` body is meant to move. So `swept: true` does not stop a fast enough ball from crossing a drum wall between frames; only the static pass contains it. Keep the speeds inside a container below its wall thickness per frame.
|
|
641
645
|
- **`'push'` pairs never publish ground or wall contact.** The bilateral resolver moves positions and exchanges velocity but does not update `onGround`, `onWallLeft` / `onWallRight`, `land`, or `wallhit`. A pile of balls resting on each other reports contact only with any separate `'block'` surface underneath it.
|
|
642
646
|
- **The `'push'` exchange has no masses, no spin and no friction.** A marble and a boulder trade velocity identically, and nothing ever starts rotating from a contact. `restitution` mixes as `max` here too, and with no mass to weight it against, that is the whole model.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Debugging and performance telemetry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reviewed on 2026-10-23 against upstream native `88e181c` and
|
|
4
|
+
`macCesar/ti.game@2bf5f82` documentation: both `GameViewProxy` implementations, `DebugHud`, `FrameStats`, both scene renderers, `example/camera.js`, `example/particles.js`, and `example/tilemap.js`.
|
|
4
5
|
|
|
5
6
|
## The two debug aids
|
|
6
7
|
|
|
@@ -32,7 +33,7 @@ The HUD is screen-space and is drawn after the camera effect. Camera scroll, zoo
|
|
|
32
33
|
- Green: collision shape after `hitboxScale`, `hitboxScaleX`, and `hitboxScaleY`.
|
|
33
34
|
- Blue: full sprite/touch bounds.
|
|
34
35
|
- Orange: anchor.
|
|
35
|
-
- Tile debug: outlines fully solid cells
|
|
36
|
+
- Tile debug: green outlines fully solid cells; yellow marks one-way top faces.
|
|
36
37
|
|
|
37
38
|
Tune geometry in this order:
|
|
38
39
|
|
|
@@ -47,7 +48,7 @@ The scale properties affect collision, not the touch rectangle. `hitboxShape: 'c
|
|
|
47
48
|
|
|
48
49
|
| HUD | Event key | Meaning |
|
|
49
50
|
| --- | --- | --- |
|
|
50
|
-
| `FPS` | `fps` | Frames
|
|
51
|
+
| `FPS` | `fps` | Frames recorded in the measurement window; Metal submission does not confirm physical presentation |
|
|
51
52
|
| `MS` | `averageCpuMs` | Average engine CPU time per frame; the `maxFps` wait is excluded |
|
|
52
53
|
| `P95` | `p95CpuMs` | 95th percentile CPU frame time |
|
|
53
54
|
| `MAX` | `maxCpuMs` | Worst CPU frame in the window |
|
|
@@ -60,8 +61,8 @@ The scale properties affect collision, not the touch rectangle. `hitboxShape: 'c
|
|
|
60
61
|
| `UPDATE` | `averageUpdateMs` | Physics, animation, tween, and scene update time |
|
|
61
62
|
| `TEXTURE` | `averageTexturePrepareMs` | Texture preparation/upload time |
|
|
62
63
|
| `BATCH` | `averageBatchMs` | Batching and drawing time |
|
|
63
|
-
| `PRESENT` | `averagePresentMs` |
|
|
64
|
-
| `PRESENTFAIL` | `presentFailures` |
|
|
64
|
+
| `PRESENT` | `averagePresentMs` | Apple only: CPU time acquiring a Metal drawable and submitting presentation; excludes GPU execution |
|
|
65
|
+
| `PRESENTFAIL` | `presentFailures` | Apple only: missing drawables and reported command errors |
|
|
65
66
|
|
|
66
67
|
The event also includes `surfaceWidth` and `surfaceHeight`. Android omits `averagePresentMs` and `presentFailures`; do not interpret missing keys as zero.
|
|
67
68
|
|
|
@@ -81,7 +82,7 @@ function cleanup() {
|
|
|
81
82
|
}
|
|
82
83
|
```
|
|
83
84
|
|
|
84
|
-
Telemetry is opt-in inside the renderers. With the HUD disabled and no `performance` listener,
|
|
85
|
+
Telemetry is opt-in inside the renderers. With the HUD disabled and no `performance` listener, telemetry timing and HUD drawing are skipped. The simulation clock still runs. Use the HUD while developing; use the event for short diagnostic sessions or a deliberate in-app performance screen, not permanent per-frame business logic.
|
|
85
86
|
|
|
86
87
|
## Reading the numbers
|
|
87
88
|
|
|
@@ -90,15 +91,23 @@ Telemetry is opt-in inside the renderers. With the HUD disabled and no `performa
|
|
|
90
91
|
- High update time: narrow collision groups and reduce active colliders/particles; hidden sprites are excluded from render and collision.
|
|
91
92
|
- A large `TileLayer` should keep draw calls and visible work stable while the camera moves; one sprite per tile scales with the whole scene instead.
|
|
92
93
|
- `maxFps: 60` can prevent a 120 Hz display from doubling work when the game does not need it.
|
|
93
|
-
- Judge final performance on hardware. The
|
|
94
|
+
- Judge final performance on hardware. The Apple backend uses Metal on iPhone, iPad, Simulator and Mac Catalyst. Simulator/host fixture results do not establish physical-device GPU timing or presentation.
|
|
95
|
+
|
|
96
|
+
The Apple Simulator keeps a 1× logical drawable; physical iPhone/iPad builds
|
|
97
|
+
use native screen scale. A frame-cap request is platform scheduling, and a
|
|
98
|
+
Metal frame can be skipped when its in-flight slot or drawable is unavailable.
|
|
99
|
+
The next successful simulation frame includes elapsed time capped at 0.1 s.
|
|
100
|
+
Do not compare telemetry across different drawable scales or call submission
|
|
101
|
+
counts a measurement of physically presented frames.
|
|
94
102
|
|
|
95
103
|
## Common diagnostic order
|
|
96
104
|
|
|
97
105
|
For a physics discrepancy, record the installed build/commit, surface size,
|
|
98
106
|
`timeScale`, input values and actual FPS first. `maxFps` is a cap, not a fixed
|
|
99
107
|
physics step. A visible thruster proves an animation state, not acceleration.
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
Older builds could erase a small first thrust step; current source preserves
|
|
109
|
+
effective acceleration and settles coasting after contacts. Rope still uses
|
|
110
|
+
per-update damping, but now freezes at zero game delta. See [physics.md](physics.md#time-step-and-frame-rate).
|
|
102
111
|
|
|
103
112
|
1. Confirm the object was added and is visible/in bounds.
|
|
104
113
|
2. Turn on the relevant sprite/layer debug shape, then the global overlay if relationships are unclear.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Module example catalog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Catalog checked against `macCesar/ti.game@2bf5f82:README.md` (Learn from the examples) and `example/app.js`: 35 demos at the reviewed snapshot, 34 touch-ready entries on phones/tablets and the additional Configurable Input entry on Mac Catalyst (`osname === 'macos'`). The launcher and `backnav.js`, `keyboard.js`, and `pool-viewport.js` are helpers, not demos. The current upstream branch is `m1ga/ti.game@88e181c`; the reviewed checkout adds documentation commit `2bf5f82`. These features are not established by the older `3bea2f4` skill pin. `wallhit` now has one user — `worldwrap.js` counts bolt hits with it — but `wallSlideSpeed` and the `onWallLeft`/`onWallRight` flags still have no demo; for those use [the platformer recipe](recipes-action.md#platformer), verified against both native implementations and the README contract.
|
|
4
4
|
|
|
5
5
|
The demos show intended API combinations; reading them does not verify an
|
|
6
6
|
installed module or device. They are **not** production templates: most keep
|
|
@@ -10,50 +10,51 @@ then apply the focused structure and cleanup patterns in this skill.
|
|
|
10
10
|
|
|
11
11
|
For equations, units, genre selection and frame-rate limits, use
|
|
12
12
|
[physics.md](physics.md). Rhythm's 400 ms interval is a quarter note at its
|
|
13
|
-
stated 150 BPM
|
|
13
|
+
stated 150 BPM; the current source now corrects the older "eighth" comments. Flappy replaces
|
|
14
14
|
vertical velocity per tap; it does not add repeated impulses. The curated
|
|
15
15
|
Lunar Lander recipe is not an additional upstream demo.
|
|
16
16
|
|
|
17
17
|
| Demo | What it proves | Production-oriented reference |
|
|
18
18
|
| --- | --- | --- |
|
|
19
|
+
| `inputmap.js` | Native configurable actions shared across keys/mouse/gamepads; focus ownership, pause contexts, movement/aim/crouch/projectiles and raw input monitor | [Shared digital actions](recipes-action.md#shared-digital-actions) |
|
|
19
20
|
| `basic.js` | Sheets, frame animation, drag/pinch/rotate, tween completion | [Scaffolding](patterns.md#the-scaffolding) |
|
|
20
|
-
| `puzzle.js` | Multi-touch drag, press-to-lift, snapping
|
|
21
|
-
| `flappy.js` | Gravity
|
|
22
|
-
| `platformer.js` | `solidWith`, `land`, `oneWay`, carried rider, camera follow, multi-touch controls, and
|
|
23
|
-
| `volley.js` | Restitution ball, `land`,
|
|
21
|
+
| `puzzle.js` | Multi-touch drag, press-to-lift, free-cell snapping/re-drag, tween-back, solved spin and Restart | [Cards and board games](recipes-adventure-effects.md#cards-and-board-games) |
|
|
22
|
+
| `flappy.js` | Gravity with replacement flap velocity, invisible score/death zones, five-gate win/reset and wrapping parallax | [Tap-to-flap](recipes-action.md#tap-to-flap) |
|
|
23
|
+
| `platformer.js` | `solidWith`, `land`, `oneWay`, carried rider, camera follow, multi-touch controls, and shared keyboard/gamepad actions | [Platformer](recipes-action.md#platformer) |
|
|
24
|
+
| `volley.js` | Restitution ball, `land`, biased player reflection, opponent score/serve, coarse AI and shared keyboard/gamepad actions | [Ball sports](recipes-action.md#ball-sports--breakout-physics) |
|
|
24
25
|
| `racing.js` | `carMode`, drift, skid marks, checkpoints and lap state | [Top-down racer](recipes-action.md#top-down-racer) |
|
|
25
|
-
| `cards.js` |
|
|
26
|
-
| `asteroids.js` | Thrust, angular velocity, wrapping,
|
|
27
|
-
| `topdown.js` |
|
|
26
|
+
| `cards.js` | Five-card deal, three-card selection, glow/wobble, Done play/re-fan and Reset sweep/redeal | [Cards and board games](recipes-adventure-effects.md#cards-and-board-games) |
|
|
27
|
+
| `asteroids.js` | Thrust, angular velocity, wrapping, momentum-inheriting pooled/additive bolts, five-rock waves, three-hit lives/invulnerability/reset and audio | [Space shooter](recipes-action.md#asteroids--space-shooter) |
|
|
28
|
+
| `topdown.js` | 12-column/15-row sprite map, animated water/solid house, nonblocking depth-sorted trees, eight-way input and companion follow/tree-state AI | [Top-down / Zelda](recipes-action.md#top-down--zelda) |
|
|
28
29
|
| `skate.js` | Endless-runner pooling, carried/uncarried terrain, parallax, emitter and audio hooks | [Endless runner](recipes-action.md#endless-runner) |
|
|
29
30
|
| `pointclick.js` | Tap-to-walk with `findPath` → `followPath`, hotspots and `ySort` | [Point-and-click](recipes-adventure-effects.md#point--click-adventure) |
|
|
30
31
|
| `particles.js` | Continuous/burst emitters, sprite target, HUD telemetry | [Particles](recipes-adventure-effects.md#particles) and [debugging](debugging-performance.md) |
|
|
31
32
|
| `worldwrap.js` | `worldWrapX` end to end: seam-aware camera, touch, overlap, solid resolution, swept movement, `wallhit`, and a full-width TileLayer that repeats across the seam | [Circular worlds](api.md#gameview) |
|
|
32
33
|
| `rhythm.js` | Pooled notes, native velocity, `press` pads, timing and audio feedback | [Rhythm game](recipes-action.md#rhythm-game) |
|
|
33
|
-
| `camera.js` | Two-axis follow, bounds, zoom, shake, camera effects, parallax and debug HUD | [Camera work](recipes-adventure-effects.md#camera-work) |
|
|
34
|
-
| `rope.js` | Rope pinned to a sprite, fixed head
|
|
34
|
+
| `camera.js` | Two-axis follow, bounds, zoom, shake, camera effects, parallax and screen-space `topRight` debug HUD | [Camera work](recipes-adventure-effects.md#camera-work) |
|
|
35
|
+
| `rope.js` | Rope pinned to a sprite, fixed head/tail weight and two draggable ends | [Ropes and chains](recipes-adventure-effects.md#ropes-and-chains) |
|
|
35
36
|
| `flip.js` | Draw-only flips driven by direction and inverted gravity | [Visual effects](recipes-adventure-effects.md#visual-effects) |
|
|
36
37
|
| `hitbox.js` | Global/sprite debug overlays and per-axis hitbox correction | [Seeing the hitbox](patterns.md#seeing-the-hitbox--hitboxjs) |
|
|
37
38
|
| `blend.js` | Normal/add/multiply/screen modes and `flash()` | [Visual effects](recipes-adventure-effects.md#visual-effects) |
|
|
38
|
-
| `text.js` | Screen-fixed HUD, world labels, text buttons, word wrap and `attachTo` | [HUD](patterns.md#hud--textjs-flappyjs) |
|
|
39
|
+
| `text.js` | Screen-fixed HUD, world labels, text buttons, word wrap, precomposed ES/DE/NL characters and `attachTo` | [HUD](patterns.md#hud--textjs-flappyjs) |
|
|
39
40
|
| `swept.js` | Identical fast bullets with and without continuous sweep | [Object pooling](patterns.md#object-pooling) and [API gotchas](api.md#gotchas-the-property-tables-do-not-show) |
|
|
40
41
|
| `path.js` | Looped/smoothed paths, one-shot paths and animation chains | [Patrol routes](patterns.md#patrol-routes-and-animation-chains--pathjs) |
|
|
41
42
|
| `raycast.js` | Line of sight, ledge probes and tap-fired hitscan | [Raycast pattern](patterns.md#line-of-sight-ledge-probes-and-hitscan--raycastjs) |
|
|
42
43
|
| `zones.js` | `collision`/`collisionend`, pressure plate and removal during contact | [Zones](patterns.md#zones-you-can-be-inside-of-enter--exit--zonesjs) |
|
|
43
|
-
| `demoscene.js` |
|
|
44
|
-
| `maze.js` | Raw/simplified A
|
|
44
|
+
| `demoscene.js` | Arc-offset per-character closed-path sine scroller, additive circle-driven copper bars, glowing logo, chained opacity pulse, period-scrolled starfield and music cleanup | [Visual effects](recipes-adventure-effects.md#visual-effects) |
|
|
45
|
+
| `maze.js` | Raw/simplified A* dots, tap routes, 800 ms game-clock re-pathing chase and catch/den reset | [Pathfinding](patterns.md#pathfinding-around-obstacles--mazejs-pointclickjs) |
|
|
45
46
|
| `timescale.js` | Slow/frozen scene, engine timer versus real-time interval | [Game-clock timers](patterns.md#timers-on-the-game-clock) |
|
|
46
47
|
| `circles.js` | Rect/circle/rotated-rect solids and `block` versus bilateral `push` | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
|
|
47
48
|
| `slopes.js` | Rotated-rect ramp normals and restitution supplied by a surface | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
|
|
48
|
-
| `plinko.js` |
|
|
49
|
-
| `pool.js` |
|
|
50
|
-
| `drum.js` | Analytic circular containment,
|
|
49
|
+
| `plinko.js` | 86 circular pegs, swept/push chips, rect dividers, trigger payouts, chosen/random one/ten drops and deduplicated impact audio | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
|
|
50
|
+
| `pool.js` | 16 bilateral push balls, rect rails, circular trigger pockets, felt damping, additive anytime shot, pair-deduplicated impact audio/reset and fitted portrait stage | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
|
|
51
|
+
| `drum.js` | Analytic circular containment, 25 push balls, shake, enter/exit-rearmed tumble strip, separate glass/ball impact audio and pair deduplication | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
|
|
51
52
|
| `wind.js` | `gravityX` as wind and as top-down constant acceleration | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
|
|
52
|
-
| `tilemap.js` | One 120×90 `TileLayer`, visible-cell rendering, cell collision, live edits, A* and HUD | [Tile maps](tilemaps.md) |
|
|
53
|
+
| `tilemap.js` | One 120×90 `TileLayer`, visible-cell rendering, cell collision, live edits, A* and HUD relayout on later resizes | [Tile maps](tilemaps.md) |
|
|
53
54
|
|
|
54
55
|
## Choosing the right map example
|
|
55
56
|
|
|
56
|
-
`topdown.js` deliberately creates one sprite per tile for a small
|
|
57
|
+
`topdown.js` deliberately creates one sprite per tile for a small 12-column by 15-row scene; that remains reasonable when individual tiles need sprite behavior. `tilemap.js` is the reference for large or mostly static grids: one native `TileLayer` avoids ticking, sorting, drawing, and collision-scanning off-screen tile sprites.
|
|
57
58
|
|
|
58
59
|
## Reading a demo safely
|
|
59
60
|
|
|
@@ -62,3 +63,85 @@ Lunar Lander recipe is not an additional upstream demo.
|
|
|
62
63
|
3. Replace `var` and function-expression callbacks with the project's current ES6+ style.
|
|
63
64
|
4. Keep resources outside `resize`; create surface-sized scene objects inside one guarded `resize` path.
|
|
64
65
|
5. Separate reusable state from launcher UI and add explicit cleanup for app-owned listeners, timers, and sounds.
|
|
66
|
+
|
|
67
|
+
## Keyboard shortcuts and shared helpers
|
|
68
|
+
|
|
69
|
+
`keyboard.js` builds a native `inputMap`, dispatches `actiondown`/`actionup`
|
|
70
|
+
to the existing gameplay functions, activates/focuses the canvas on window
|
|
71
|
+
open/focus, and sets `inputContext = null` on blur/close. It removes its two
|
|
72
|
+
action listeners on close. It also focuses immediately because a guarded
|
|
73
|
+
scene `resize` may initialize after `Window.open`. This helper replaces the
|
|
74
|
+
entire map; merge bindings in app code if the view already owns other actions.
|
|
75
|
+
Platformer and Volley route mapped gamepad buttons through the same actions;
|
|
76
|
+
do not also dispatch those actions from `buttondown`/`buttonup`.
|
|
77
|
+
|
|
78
|
+
| Demos | Keyboard shortcuts |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| Platformer, Volley | Left/Right, A/D or J/L move; Space/Up/W/I jump; pad d-pad/left stick and A/up use the same actions |
|
|
81
|
+
| Flappy, Flip | Space/Up/W flap or restart / invert gravity |
|
|
82
|
+
| Skate | Space/Up/W jump or restart |
|
|
83
|
+
| Plinko | Space/1 drops one chip; 0 drops ten |
|
|
84
|
+
| Pool | R resets; mouse/touch aims |
|
|
85
|
+
| Drum | Space shakes; T toggles tumble |
|
|
86
|
+
| Circles | Space shoots; D toggles shapes |
|
|
87
|
+
| Slopes | R resets; D toggles shapes |
|
|
88
|
+
| Zones | Space/R removes or respawns the ball |
|
|
89
|
+
| Text | Space scores; R resets; N advances dialog |
|
|
90
|
+
| Tilemap | B toggles building; D toggles debug; mouse/touch selects cells |
|
|
91
|
+
| Wind | Space drops; Left/Right or A/D changes wind |
|
|
92
|
+
| Particles, Blend | Space creates a centered burst / flashes ships |
|
|
93
|
+
| Rhythm | A/S/D or Left/Down/Right hits the lanes |
|
|
94
|
+
| Swept | Space shoots both lanes; Left/Right or A/D changes speed |
|
|
95
|
+
| Camera | Z/X zooms out/in; Space shakes |
|
|
96
|
+
| Timescale | 1/2/3/4 selects 1/0.5/0.1/0 speed |
|
|
97
|
+
|
|
98
|
+
Configurable Input has its own map: A/D, J/L or arrows move; last pressed
|
|
99
|
+
left/right wins and releasing it restores the other held direction. Space
|
|
100
|
+
or pad A jumps once from the ground. W/I/Up aims up (with horizontal movement
|
|
101
|
+
for diagonal shots), S/K/Down crouches while grounded, left canvas click or
|
|
102
|
+
pad X fires. The normalized direction keeps diagonal projectile speed equal
|
|
103
|
+
to straight shots; crouching changes height while preserving foot position.
|
|
104
|
+
Escape switches between gameplay/paused contexts and toggles `timeScale`.
|
|
105
|
+
The right/middle mouse bindings are test entries: the reviewed docs report
|
|
106
|
+
that they do not trigger on Mac; left click is the verified path.
|
|
107
|
+
|
|
108
|
+
Instructions remain separate from changing action feedback. The monitor
|
|
109
|
+
shows physical key codes/modifiers and pad buttons/sticks/triggers. Focusing
|
|
110
|
+
the native text field disables actions and clears held movement; window
|
|
111
|
+
focus does not steal that text focus. The focus button or first canvas click
|
|
112
|
+
restores game input; that first click does not shoot. Blur/close disables
|
|
113
|
+
actions, close cancels projectile-expiry game timers and unloads the four
|
|
114
|
+
owned sheets. Run the indirect-input configuration from [project setup](project-setup.md)
|
|
115
|
+
before mouse testing on the supported older Apple systems. Physical peripheral
|
|
116
|
+
and overlay acceptance is separate from compilation and resolver fixtures.
|
|
117
|
+
|
|
118
|
+
## Resizable scenes and portrait Pool
|
|
119
|
+
|
|
120
|
+
The current demos size foreground art against both surface dimensions rather
|
|
121
|
+
than letting a wide Mac window enlarge actors beyond its height. Scenes with
|
|
122
|
+
native overlay controls derive pixels per view point from
|
|
123
|
+
`resize.width / gameView.rect.width`, with a positive finite check and a
|
|
124
|
+
logical-density fallback; monitor/display height is not the GameView size.
|
|
125
|
+
|
|
126
|
+
Pool alone uses `pool-viewport.js`: a 380 by 680 point portrait stage contains
|
|
127
|
+
both GameView and native Reset control. Host `postlayout` applies a uniform
|
|
128
|
+
`min(hostWidth / 380, hostHeight / 680)` transform and removes that listener on
|
|
129
|
+
close. Window resizing fits the running stage; it does not recreate balls,
|
|
130
|
+
timers, collision bounds or drag state. Tilemap instead retains its native
|
|
131
|
+
map and relays later `resize` values to HUD layout. Plinko fits chip/peg sizes
|
|
132
|
+
to horizontal, diagonal and two-row gaps while preserving their proportions;
|
|
133
|
+
wide/short scenes must still leave chip-sized passages.
|
|
134
|
+
|
|
135
|
+
## Running and verification boundaries
|
|
136
|
+
|
|
137
|
+
From the module's `android/`, use `ti build -p android --sdk 13.4.1.GA` for
|
|
138
|
+
the launcher. On Apple, build the module, copy `example/` into a Classic app's
|
|
139
|
+
`Resources/` and register the `iphone` module; keep the three helpers when
|
|
140
|
+
copying demos that require them. Mac lists Configurable Input; mobile retains
|
|
141
|
+
34 touch-ready entries, with the input demo source available for external
|
|
142
|
+
peripheral tests. Back uses action-bar Up on Android and a shared overlay on Apple.
|
|
143
|
+
|
|
144
|
+
The catalog is not exhaustive API acceptance: no dedicated wall-slide/jump or
|
|
145
|
+
`unload()` demo exists, nor coverage for every gamepad event or creation-time
|
|
146
|
+
option. `worldwrap.js` does exercise `wallhit`. Use native-contract references
|
|
147
|
+
for uncovered members and verify the actual installed binary on the target.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Keyboard, mouse and action maps
|
|
2
|
+
|
|
3
|
+
Reviewed 2026-10-23 against `m1ga/ti.game@88e181cb85526d8146d0be6e09b2b90c2551baad` native input resolvers and canvas adapters, with README clarification from `macCesar/ti.game@2bf5f828a3bb8028eed20d6ba27b97dd3aa9a753`. Requires an artifact containing those APIs; a shared version number is insufficient evidence. Existing controller events and analog input remain documented in [api.md](api.md#gamepads).
|
|
4
|
+
|
|
5
|
+
## Map and event contract
|
|
6
|
+
|
|
7
|
+
`GameView.inputMap` is optional, live, readable/writable and defaults to `{}`. It maps contexts to named actions, each with an array of digital bindings. `inputContext` accepts only a string or `null` (other types throw) and defaults to `'gameplay'`; `null` disables actions. Replace the whole map to rebind; the module supplies no settings UI or persistence.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const gameView = Game.createGameView({
|
|
11
|
+
inputMap: {
|
|
12
|
+
gameplay: {
|
|
13
|
+
thrust: [
|
|
14
|
+
{ device: 'keyboard', code: 'KeyW' },
|
|
15
|
+
{ device: 'keyboard', code: 'ArrowUp' }
|
|
16
|
+
],
|
|
17
|
+
fire: [
|
|
18
|
+
{ device: 'keyboard', code: 'Space' },
|
|
19
|
+
{ device: 'mouse', button: 'left' },
|
|
20
|
+
{ device: 'gamepad', button: 'a' }
|
|
21
|
+
],
|
|
22
|
+
pause: [{ device: 'keyboard', code: 'Escape' }]
|
|
23
|
+
},
|
|
24
|
+
paused: { pause: [{ device: 'keyboard', code: 'Escape' }] }
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
gameView.addEventListener('actiondown', ({ action, context }) => {
|
|
28
|
+
// Dispatch discrete actions or write native motion properties here.
|
|
29
|
+
// A pause handler can change inputContext to 'paused' and timeScale to 0.
|
|
30
|
+
});
|
|
31
|
+
gameView.addEventListener('actionup', ({ action, context }) => {
|
|
32
|
+
// Clear held controls, including releases caused by lost focus/cancellation.
|
|
33
|
+
});
|
|
34
|
+
// Add the view, open the window, then call this (also after native text entry):
|
|
35
|
+
gameView.focusInput();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The snippet is a map/transition template; scene setup and window lifecycle are in [project-setup.md](project-setup.md). No per-frame JavaScript polling is needed: action resolution runs natively on input transitions.
|
|
39
|
+
|
|
40
|
+
| Input | Contract |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `actiondown` / `actionup` | Payload `{ action, context }`. An action goes down on its first active binding and up after its last source releases, across different bindings and controllers |
|
|
43
|
+
| Map replacement | A valid map releases previous active actions and requires a fresh press; held keys cannot activate the replacement map until released and pressed again |
|
|
44
|
+
| Context change | Releases active actions from the old context, requires a fresh press; assigning the same context leaves state unchanged |
|
|
45
|
+
| Invalid map | Throws without replacing the map or active state |
|
|
46
|
+
| Cancellation | Focus loss, suspension, device disconnection and view shutdown release held actions. An action release need not have a physical/raw `keyup`; drive gameplay from action transitions |
|
|
47
|
+
| Auto-repeat | Ignored as a new press; holding a key does not repeat `actiondown` |
|
|
48
|
+
|
|
49
|
+
Context and action names must be nonempty strings. Empty contexts and empty action arrays are allowed. Each binding has **exactly two keys**: `device` plus `code` for keyboard, or `device` plus `button` for mouse/gamepad. Analog axes are not action bindings.
|
|
50
|
+
|
|
51
|
+
| Device | Accepted binding values |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `keyboard` (`code`) | `KeyA`…`KeyZ`, `Digit0`…`Digit9`, `ArrowUp`, `ArrowDown`, `ArrowLeft`, `ArrowRight`, `Space`, `Escape`, `Enter`, `Tab`, `Backspace`, `Delete`, `Home`, `End`, `PageUp`, `PageDown`, `F1`…`F12`, `ShiftLeft`/`ShiftRight`, `ControlLeft`/`ControlRight`, `AltLeft`/`AltRight`, `MetaLeft`/`MetaRight` |
|
|
54
|
+
| `mouse` (`button`) | `left`, `middle`, `right` |
|
|
55
|
+
| `gamepad` (`button`) | `a`, `b`, `x`, `y`, `l1`, `r1`, `l2`, `r2`, `l3`, `r3`, `start`, `select`, `home`, `up`, `down`, `left`, `right` |
|
|
56
|
+
|
|
57
|
+
Existing gamepad properties, individual button events, sticks and triggers are unchanged. Use mapped actions **or** individual button handlers for each gameplay action to avoid dispatching twice. An app keeping its own gamepad adapter can omit gamepad bindings from the map.
|
|
58
|
+
|
|
59
|
+
## Keyboard focus and pointer ownership
|
|
60
|
+
|
|
61
|
+
Raw `keydown` / `keyup` events carry `{ code, character, modifiers: { shift, control, alt, meta } }`. `code` identifies the physical key independently of its text character; `character` can be empty for non-text keys.
|
|
62
|
+
|
|
63
|
+
Only the focused GameView receives keyboard events. `focusInput()` requests canvas focus on the UI thread; it neither opens a software keyboard nor captures keys globally. Native text fields retain their focus. Call it after opening the game window and after returning from a native text field.
|
|
64
|
+
|
|
65
|
+
When the active context has mouse bindings, pointer gestures delivered to the canvas feed mapped actions and are consumed rather than also becoming sprite tap/drag interactions. Empty maps, keyboard-only contexts and disabled actions preserve existing sprite pointer interaction. Overlay Titanium controls retain their clicks and focus; the module does not capture their mouse events. Android retains the engine touch listener.
|
|
66
|
+
|
|
67
|
+
For iOS/iPadOS 13.4–16 pointer identification, set the consuming application's `tiapp.xml` opt-in (iOS 17+ defaults it to true):
|
|
68
|
+
|
|
69
|
+
```xml
|
|
70
|
+
<ios>
|
|
71
|
+
<plist>
|
|
72
|
+
<dict>
|
|
73
|
+
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
74
|
+
<true/>
|
|
75
|
+
</dict>
|
|
76
|
+
</plist>
|
|
77
|
+
</ios>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This application-level setting distinguishes indirect pointers from direct touch; the module does not change global input settings at runtime. The current Apple package targets iOS/iPadOS **15+** and Mac Catalyst with Metal. Its keyboard/pointer guards dating from iOS 13.4 and disconnection notification guards dating from iOS 14 do not lower that package minimum.
|
|
81
|
+
|
|
82
|
+
## Runnable example and acceptance limits
|
|
83
|
+
|
|
84
|
+
`example/inputmap.js` is the current action-map demo. The launcher exposes it on Mac only; mobile builds retain touch-ready demos. The source remains usable for external-peripheral testing on mobile.
|
|
85
|
+
|
|
86
|
+
- A/D, J/L, arrows or gamepad move the character. The last pressed direction wins; releasing it resumes the other held direction.
|
|
87
|
+
- Space or gamepad A jumps. W/I/Up aims upward, including diagonal combinations; S/K/Down crouches.
|
|
88
|
+
- Left canvas click or gamepad X fires. Right/middle mouse bindings are accepted for testing but currently do not trigger actions on Mac; left click is verified by the source's acceptance notes.
|
|
89
|
+
- Escape switches pause/resume action contexts. A separate monitor shows raw key codes/modifiers and existing gamepad buttons, sticks and triggers.
|
|
90
|
+
- Instructions and action feedback occupy separate areas. An overlaid text field and focus button demonstrate native ownership; clicking the canvas after text entry restores game focus, and the first click does not fire. The shared back control returns to the launcher.
|
|
91
|
+
|
|
92
|
+
Upstream verification commands, run from the module checkout:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
python3 tests/input-map/run.py
|
|
96
|
+
python3 tests/input-map/uikit.py
|
|
97
|
+
python3 tests/gamepad-fixture/check.py
|
|
98
|
+
node --test tests/example-input/controls.test.js
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Resolver regressions test native binding validation, aggregation and transitions. UIKit fixtures exercise production responder methods with renderer/event sinks for focus, raw-key repetition/cancellation and pointer/overlay ownership. GameController fixtures cover receiver fanout, disconnect and action delivery without individual listeners. JavaScript example tests check demo control behavior. Compilation and these fixtures do not establish physical keyboard, mouse, controller or overlay behavior on every supported device; record that acceptance separately.
|