@maccesar/titools 4.18.0 → 4.20.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 (28) hide show
  1. package/package.json +1 -1
  2. package/skills/purgetss/SKILL.md +9 -4
  3. package/skills/purgetss/references/animation-advanced.md +93 -226
  4. package/skills/purgetss/references/animation-system.md +26 -17
  5. package/skills/purgetss/references/app-branding.md +31 -8
  6. package/skills/purgetss/references/appearance-module.md +63 -9
  7. package/skills/purgetss/references/class-categories.md +2 -2
  8. package/skills/purgetss/references/class-index.md +1 -1
  9. package/skills/purgetss/references/classic-projects.md +24 -5
  10. package/skills/purgetss/references/cli-commands.md +59 -30
  11. package/skills/purgetss/references/custom-fonts.md +29 -2
  12. package/skills/purgetss/references/customization-deep-dive.md +20 -10
  13. package/skills/purgetss/references/dynamic-component-creation.md +1 -1
  14. package/skills/purgetss/references/icon-fonts.md +3 -1
  15. package/skills/purgetss/references/installation-setup.md +5 -1
  16. package/skills/purgetss/references/multi-density-images.md +35 -5
  17. package/skills/purgetss/references/purgetss-ui-classic.md +341 -0
  18. package/skills/purgetss/references/version-history.md +17 -0
  19. package/skills/ti-game/SKILL.md +5 -2
  20. package/skills/ti-game/references/api.md +38 -4
  21. package/skills/ti-game/references/debugging-performance.md +1 -1
  22. package/skills/ti-game/references/examples.md +6 -5
  23. package/skills/ti-game/references/patterns.md +1 -1
  24. package/skills/ti-game/references/project-setup.md +7 -3
  25. package/skills/ti-game/references/recipes-action.md +1 -1
  26. package/skills/ti-game/references/recipes-adventure-effects.md +1 -1
  27. package/skills/ti-game/references/roadmap.md +8 -4
  28. package/skills/ti-game/references/tilemaps.md +1 -1
@@ -8,7 +8,7 @@ allowed-tools: Read, Grep, Glob, Edit, Write, Bash(node *)
8
8
 
9
9
  `ti.game` is Michael Gangolf's OpenGL ES 2.0 sprite engine for Titanium. JavaScript describes the scene and reacts to discrete events; Android and iOS run rendering, animation, physics, collision, gestures, audio, particles, ropes, tile layers, camera work, and telemetry natively.
10
10
 
11
- This skill is verified against upstream `main` at **`c216e7f` (2026-08-28)**. Both manifests still say **`0.5.0`**, although important features landed after that version was introduced. Treat the commit or build date as the capability marker; feature-detect an installed build when the exact artifact is unknown.
11
+ This skill is verified against upstream `main` at **`3bea2f4` (2026-09-02)**. Both manifests say **`0.6.0`**, which is unreleased; the published **`0.5.0`** release carries the same engine code, because its tag sits on the commit immediately before the version bump. Treat the commit or build date as the capability marker, never the version string — a locally built artifact stamped `0.5.0` can predate that release by weeks. Feature-detect an installed build when the exact artifact is unknown.
12
12
 
13
13
  The skill applies to **Classic and Alloy** projects. Nothing here requires PurgeTSS.
14
14
 
@@ -56,6 +56,9 @@ Do not move sprites from `setInterval`, `requestAnimationFrame`, or a JS loop. P
56
56
  | Chain, tether, cape | `Game.createRope()` |
57
57
  | Pause or slow the scene while still drawing | `gameView.timeScale` |
58
58
  | Measure FPS, frame time, and draw calls | `debug: { hud: true }` or the `performance` event |
59
+ | Read a game controller | GameView `buttondown`/`buttonup`, `stick`, `trigger` (names normalized across pads) |
60
+ | Wrap the world around horizontally | `gameView.worldWrapX` + per-sprite `wrapWorldX` |
61
+ | React to the force of a hit | `solidimpact` with a per-sprite `impactThreshold` |
59
62
 
60
63
  ## Minimal scene
61
64
 
@@ -124,7 +127,7 @@ When sources disagree, use this order:
124
127
  3. Upstream `README.md` and `TUTORIAL.md` for intended public usage.
125
128
  4. Upstream `TODO.md` for what remains partial or absent.
126
129
 
127
- Known upstream prose drift at the pinned commit: the README says 26 demos although there are 33; its camera row names the wrong HUD corner; and it says every `TileLayer` property is live although Android treats `legend`, `firstGid`, `cols`, and `rows` as creation-time configuration. The references already normalize those differences. No demo exercises the wall-contact API yet, so its contract comes from the README and both native implementations.
130
+ The prose drift recorded at the previous pin is gone: upstream now states 34 demos (correct), names the HUD corners correctly, and documents `legend`, `firstGid`, `cols` and `rows` as creation-time on Android. What remains uncovered by any demo is `wallSlideSpeed` and the `onWallLeft`/`onWallRight` flags — `wallhit` itself is now exercised by `worldwrap.js` — so that part of the contract still comes from the README and both native implementations.
128
131
 
129
132
  ## Before returning code
130
133
 
@@ -1,6 +1,6 @@
1
1
  # ti.game API reference
2
2
 
3
- Complete JS surface of `ti.game` as of upstream `main` at `c216e7f`, 2026-08-28, verified against every Android proxy/engine and its iOS twin. Defaults come from native fields, not prose. The manifests still read **`0.5.0`**, but the performance HUD/telemetry, native `TileLayer`, and wall-contact API landed afterward without another lasting bump. Read the number as a floor, never as a feature list. Older `0.3.0`/`0.4.0` artifacts also vary by build date; feature-detect when the artifact is unknown. Read a property before writing it, because Kroll can retain an unknown write on the proxy. Methods are safer probes (`typeof Game.createTileLayer === 'function'`), as are normalized properties (`solidMode` reads an unknown name back as `'block'` only when the engine implements it).
3
+ Complete JS surface of `ti.game` as of upstream `main` at `3bea2f4`, 2026-09-02, verified against every Android proxy/engine and its iOS twin. Defaults come from native fields, not prose. The manifests read **`0.6.0`**, which is unreleased: the published **`0.5.0`** release carries the same engine code, because its tag sits on the commit right before the version bump. Read the number as a floor, never as a feature list — a locally built artifact stamped `0.5.0` may predate that release by weeks. Older `0.3.0`/`0.4.0` artifacts also vary by build date; feature-detect when the artifact is unknown. Read a property before writing it, because Kroll can retain an unknown write on the proxy. Methods are safer probes (`typeof Game.createTileLayer === 'function'`), as are normalized properties (`solidMode` reads an unknown name back as `'block'` only when the engine implements it).
4
4
 
5
5
  Properties are live unless their table says creation-time or read-only. Every writable property can be passed to its `create*` factory. `TileLayer` has four cross-platform creation-time inputs (`legend`, `firstGid`, `cols`, `rows`) because Android does not expose the live setters that iOS has. All durations crossing the JS boundary are **milliseconds**.
6
6
 
@@ -10,6 +10,7 @@ Properties are live unless their table says creation-time or read-only. Every wr
10
10
  - [Module](#module)
11
11
  - [Names and percentages](#names-and-percentages)
12
12
  - [GameView](#gameview)
13
+ - [Gamepads](#gamepads)
13
14
  - [SpriteSheet](#spritesheet)
14
15
  - [Sprite](#sprite)
15
16
  - [Font](#font)
@@ -106,6 +107,7 @@ A normal Titanium view — add it to a window or any container, size it with the
106
107
  | `cameraX` / `cameraY` | float | `0` | World-space offset of the view |
107
108
  | `cameraScale` | float | `1` | Zoom, anchored on the view center |
108
109
  | `cameraBounds` | dict / null | `null` | `{ minX, minY, maxX, maxY }` world rect the visible area is clamped into |
110
+ | `worldWrapX` | dict / null | `null` | `{ minX, maxX }` turns on a finite circular horizontal world; sprites opt in with `wrapWorldX`. Invalid or non-positive intervals disable it. **Horizontal `cameraBounds` are ignored while it is active**; the vertical limits still apply |
109
111
  | `follow(sprite, options)` | method | — | Native dead-zone follow, see below |
110
112
  | `stopFollow()` | method | — | Stop following; the camera stays where it is |
111
113
  | `shake({ strength, duration })` | method | `12` px, `400` ms | Detuned-sine rumble on the projection only — follow, bounds and touch mapping are unaffected |
@@ -117,12 +119,18 @@ A normal Titanium view — add it to a window or any container, size it with the
117
119
  | `cameraEffect` | string | `'none'` | Fullscreen shader pass: `'none'`, `'tint'`, `'glitch'`. With `'none'` the extra pass is skipped entirely |
118
120
  | `cameraTint` | string | — | Color for the `'tint'` effect, e.g. `'#4f8'` (night vision, poison, flashback) |
119
121
  | `cameraEffectIntensity` | float | `1` | 0..1 — tint mix or glitch amount |
122
+ | `gamepads` | array | `[]` | Read-only. Connected controllers as `[{ id, name }]` |
123
+ | `gamepad` | dict / null | `null` | Read-only snapshot of the most recently used pad: `{ id, name, leftX, leftY, rightX, rightY, l2, r2, buttons }` |
124
+ | `gamepadDeadzone` | float | `0.2` | Radial stick dead zone with rescaling, clamped to 0..0.9, so small values start at 0 instead of jumping |
125
+ | `gamepadStickPress` / `gamepadStickRelease` | float | `0.5` / `0.4` | Left-stick thresholds for reporting a direction as a button. Press clamps to 0.1..0.95; release stays between 0.05 and press. The gap is hysteresis against flicker |
120
126
  | `debug` | bool / dict | `false` | `true` draws all collision shapes. Object form: `{ hitbox, hud, hudFont }`; `hud` is `true` or a corner name. Readback is normalized to `{ hitbox, hud: false | corner }`. See [debugging-performance.md](debugging-performance.md) |
121
127
 
122
128
  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).
123
129
 
124
130
  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.
125
131
 
132
+ 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.
133
+
126
134
  ### `follow(sprite, options)`
127
135
 
128
136
  | Option | Default | Effect |
@@ -165,6 +173,25 @@ if (path) {
165
173
  }
166
174
  ```
167
175
 
176
+ ## Gamepads
177
+
178
+ A Bluetooth or USB controller (Xbox, PlayStation, Stadia, any MFi pad) reaches JS as discrete GameView events. There is no polling and no per-frame traffic; `gameView.gamepad` exists for the rare case where reading from a `gameView.every()` timer fits better than reacting to events.
179
+
180
+ | Event | Payload | Notes |
181
+ | --- | --- | --- |
182
+ | `buttondown` / `buttonup` | `button`, `input`, `gamepad`, `keyCode` | `button` is normalized across pads: `a` `b` `x` `y` `l1` `r1` `l2` `r2` `l3` `r3` `start` `select` `home` `up` `down` `left` `right`. iOS reports `keyCode: 0` |
183
+ | `stick` | `stick` (`'left'`/`'right'`), `x`, `y`, `gamepad` | Analog, throttled to ~20 Hz per channel while the value changes |
184
+ | `trigger` | `trigger` (`'l2'`/`'r2'`), `value`, `gamepad` | Analog travel, same ~20 Hz throttle |
185
+ | `gamepadconnected` / `gamepaddisconnected` | `gamepad`, `name` | A pad already paired when the view was created announces itself on its first input, so a connected event always precedes that pad's buttons |
186
+
187
+ The four directions arrive **both** from the d-pad (however the pad reports it, keys or hat) and from the left stick once it crosses `gamepadStickPress`, releasing below `gamepadStickRelease`. Each name is reported down once, however many physical controls map to it, so one `buttondown` table covers both without asking which the player used; `input` (`'dpad'`, `'leftstick'`, `'button'`, `'trigger'`) tells them apart when that matters. Analog triggers count as `l2`/`r2` buttons past half travel *and* fire `trigger` events.
188
+
189
+ Transitions skip the throttle: leaving rest, returning to rest and crossing zero are reported the moment they happen, and a channel always ends on its rest value — a released stick reports `0, 0` rather than leaving the hero drifting. `y` follows the engine's y-down convention, so `-1` is up. Everything held is released (a `buttonup`, plus a final rest-value `stick`/`trigger`) when the app backgrounds or the pad disconnects, so a direction never sticks.
190
+
191
+ On Android the buttons are captured at the activity window: the game view needs no focus, and mapped buttons are consumed so `b` does not act as Back and the d-pad does not move focus between overlay buttons. Unmapped keys (Back, volume) pass through. On Android 11+ the module opts the window out of joystick motion batching, so stick changes arrive as the pad reports them instead of once per display frame.
192
+
193
+ `platformer.js` runs on either the on-screen buttons or a pad.
194
+
168
195
  ## SpriteSheet
169
196
 
170
197
  Create one sheet per texture and share it across every sprite that uses it — sprites sharing a sheet batch into a single draw call.
@@ -231,6 +258,10 @@ Methods: `play(name)` starts an animation, `stop()` halts it on the current fram
231
258
  | `rotatable` | `false` | Two-finger rotate |
232
259
  | `touchEnabled` | `true` | `false` = touches pass through to sprites underneath |
233
260
 
261
+ `drag`, `pinch` and `rotate` all cross the bridge at ~10 Hz, and each one always delivers its final value when the gesture ends, so the value a handler last saw is the value the sprite kept. Before `9c45cf6` only `drag` was throttled and the other two fired per motion event. The two-finger rotation delta also takes the short way round the +/-180 degree flip from that commit on; earlier builds could add a whole turn in one move.
262
+
263
+ The touch slop that separates a tap from a drag is a screen-pixel distance divided by `cameraScale`, so a tap stays a tap at any zoom. `screenFixed` sprites keep the raw pixel slop, since they already live in screen space. Earlier builds compared a pixel constant against world units, which made taps too forgiving when zoomed out and too strict when zoomed in.
264
+
234
265
  Hit-testing runs against the transformed shape (rotation and scale included), topmost first, and is multi-touch: each finger runs its own gesture and a sprite belongs to at most one finger. A second finger landing on empty space — or on the sprite already held — pinches/rotates that sprite instead, per its flags.
235
266
 
236
267
  ### Physics
@@ -245,6 +276,7 @@ Hit-testing runs against the transformed shape (rotation and scale included), to
245
276
  | `angularVelocity` | `0` | deg/s |
246
277
  | `thrust` | `0` | px/s² along the current heading (Newtonian flight) |
247
278
  | `wrapAround` | `false` | Re-enter from the opposite screen edge (Asteroids) |
279
+ | `wrapWorldX` | `false` | Opt this sprite into the GameView's circular `worldWrapX` interval. Native movement normalizes `x` inside it; rendering, touch, sprite overlap, solid resolution and swept movement all choose the nearest periodic image when **both** sprites participate. Takes precedence over `wrapAround`, `wrapX` and `wrapShift`; `screenFixed` sprites are excluded. A `TileLayer` repeats across the seam only when its `x` equals `minX` and its width equals `maxX - minX`. `raycast()`, `findPath()`, particles, ropes and skid trails do **not** repeat periodically |
248
280
  | `wrapX`, `wrapShift` | `0` | Scroll looping: at `x < wrapX`, add `wrapShift`. Two screen-wide copies with `{ wrapX: -W/2, wrapShift: 2*W }` and a negative `velocityX` make a seamless parallax layer with no JS in the loop |
249
281
 
250
282
  ### Solids
@@ -257,6 +289,7 @@ Hit-testing runs against the transformed shape (rotation and scale included), to
257
289
  | `wallSlideSpeed` | `0` | Maximum downward velocity in px/s while either wall flag is true. `0` disables the cap. Upward velocity and slower falls are unchanged |
258
290
  | `restitution` | `0` | 0..1 bounce factor. Read off **both** sides of the contact and mixed as `max(mover, solid)`, the way Box2D does it — so a springy floor bounces riders that are not themselves bouncy. Solids default to 0, so a scene that never sets it on a surface behaves exactly as it did before this existed. A bounce whose closing speed would come out under **40 px/s** is damped to a dead stop instead, so a settling body grounds rather than buzzing |
259
291
  | `solidMode` | `'block'` | On the **solid**. `'block'` is an immovable wall (what every solid was). `'contain'` is an inward circular boundary — matched circles are kept *inside* its circumference (drums, bowls, lottery cages). `'push'` makes it a body in its own right: a matched circle and this one split the separation and exchange the closing velocity at equal mass. `'contain'` and `'push'` are **circle-on-circle only**; anything else, and any unknown string, falls back to `'block'`. Since 2026-08-27 |
292
+ | `impactThreshold` | `40` | Minimum compensated relative normal speed in px/s before this receiver fires `solidimpact`. Minimum 0. Each participant applies its own threshold and owns its own gate, which rearms only after more than 100 ms of confirmed separation in game time |
260
293
  | `oneWay` | `false` | On the **solid**: pass-through except for landings on its top edge — classic platformer floors. Honoured by `'block'` solids only |
261
294
  | `carryRiders` | `true` | On the **solid**: a moving solid carries whoever stands on it (velocity, tweens, idle wobble — wrap teleports excluded). Set `false` for world-scroll terrain that moves while the player is meant to stay put |
262
295
 
@@ -284,7 +317,7 @@ Two consequences that cost an afternoon each if you meet them by surprise:
284
317
  | Property | Default | Notes |
285
318
  | --- | --- | --- |
286
319
  | `collisionGroup` | — | The tag *this* sprite carries |
287
- | `collidesWith` | — | Array of groups this sprite reports overlaps with |
320
+ | `collidesWith` | — | Array of groups this sprite reports overlaps with. Since `9c45cf6` a single group name is accepted as shorthand for a one-element array, on this property and on `solidWith`; `null` clears either. Earlier builds required the array and threw on a bare string |
288
321
  | `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%'` |
289
322
  | `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 |
290
323
  | `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 |
@@ -393,6 +426,7 @@ The rest of the contract, all read from the engine:
393
426
  | `collision` | `group`, `other`, `x`, `y` | Overlap with a `collidesWith` group began |
394
427
  | `collisionend` | `group`, `other`, `x`, `y` | That overlap ended: the shapes separated, **or** the partner was removed from the scene, hidden (`visible = false`) or stopped matching the group filter |
395
428
  | `land` | `x`, `y`, optional `other`, optional `group` | Landed on top of a `solidWith` solid. `other` and `group` identify the solid sprite when one exists; TileLayer cells have no proxy and omit both |
429
+ | `solidimpact` | `group`, `other`, `x`, `y`, `contactX`, `contactY`, `normalX`, `normalY`, `speed`, `restitution` | A sprite-to-sprite `solidWith` response met this receiver's `impactThreshold`. Both sprites in the pair get the event, sharing the contact point and `speed` and carrying **opposite** normals; `x`/`y` is the receiver's own position. `speed` is the relative closing normal speed *before* restitution, compensated for the gravity, thrust, car model and damping applied that frame, and `restitution` is the effective mixed value. `other` can be `null` if its proxy was released before delivery. **TileLayer cells never emit it.** Contact points are exact for circles and face contacts; an OBB corner is a support-point approximation. Spin, surface velocity, tweens and `followPath` add no physical normal velocity, so they never generate it on their own. With no listener on either sprite the resolver returns before computing any of this, so an unused event costs nothing |
396
430
  | `wallhit` | `side`, `x`, `y`, optional `other`, optional `group` | Began pressing a matching wall or switched sides. `side` is `'left'` or `'right'`. TileLayer cells omit both `other` and `group`; there is no `wallend` event, so read the flags in the jump handler |
397
431
 
398
432
  ## Font
@@ -571,8 +605,8 @@ Movers list the layer's `collisionGroup` in `solidWith`. Rect, circle, swept, re
571
605
 
572
606
  Only these events exist. Nothing fires per frame, and events are only fired if a listener is registered.
573
607
 
574
- - **GameView**: `press`, `tap`, `release`, `resize`, `timer`, `performance`
575
- - **Sprite** (and therefore **Text**): `press`, `release`, `tap`, `dragstart`, `drag`, `dragend`, `pinch`, `rotate`, `animationcomplete`, `complete`, `pathcomplete`, `collision`, `collisionend`, `land`, `wallhit`
608
+ - **GameView**: `press`, `tap`, `release`, `resize`, `timer`, `performance`, `buttondown`, `buttonup`, `stick`, `trigger`, `gamepadconnected`, `gamepaddisconnected`
609
+ - **Sprite** (and therefore **Text**): `press`, `release`, `tap`, `dragstart`, `drag`, `dragend`, `pinch`, `rotate`, `animationcomplete`, `complete`, `pathcomplete`, `collision`, `collisionend`, `land`, `wallhit`, `solidimpact`
576
610
  - **SpriteSheet, Font, Sound, Emitter, Rope, TileLayer**: none
577
611
 
578
612
  There is `collision` (enter) and `collisionend` (exit), but deliberately **no** per-frame "stay" event — that would be bridge traffic every frame. Hold the in-between state in JS: you heard the enter, you will hear the end.
@@ -1,6 +1,6 @@
1
1
  # Debugging and performance telemetry
2
2
 
3
- Verified against `ti.game@c216e7f`: both `GameViewProxy` implementations, `DebugHud`, `FrameStats`, both scene renderers, `example/camera.js`, `example/particles.js`, and `example/tilemap.js`.
3
+ Verified against `ti.game@3bea2f4`: both `GameViewProxy` implementations, `DebugHud`, `FrameStats`, both scene renderers, `example/camera.js`, `example/particles.js`, and `example/tilemap.js`.
4
4
 
5
5
  ## The two debug aids
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Upstream example catalog
2
2
 
3
- Verified against all 33 self-contained demos under `ti.game@c216e7f:example/` plus the launcher `example/app.js`. Wall contacts and `wallSlideSpeed` are not represented by a demo at this commit; use [the platformer recipe](recipes-action.md#platformer), which is verified against both native implementations and the README contract.
3
+ Verified against all 34 self-contained demos under `ti.game@3bea2f4:example/`, plus the launcher `example/app.js` and the shared back-control helper `example/backnav.js` (neither is a demo). `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 prove that API combinations run together on the current module. They are **not** production templates: most keep state in one file, repeat launcher/back-button plumbing, and still use pre-ES6 syntax. Start from the closest demo, retain the demonstrated engine calls, then apply the focused structure and cleanup patterns in this skill.
6
6
 
@@ -9,7 +9,7 @@ The demos prove that API combinations run together on the current module. They a
9
9
  | `basic.js` | Sheets, frame animation, drag/pinch/rotate, tween completion | [Scaffolding](patterns.md#the-scaffolding) |
10
10
  | `puzzle.js` | Multi-touch drag, press-to-lift, snapping and tween-back | [Cards and board games](recipes-adventure-effects.md#cards-and-board-games) |
11
11
  | `flappy.js` | Gravity impulse, invisible score/death zones, wrapping parallax | [Tap-to-flap](recipes-action.md#tap-to-flap) |
12
- | `platformer.js` | `solidWith`, `land`, `oneWay`, carried rider, camera follow, multi-touch controls | [Platformer](recipes-action.md#platformer) |
12
+ | `platformer.js` | `solidWith`, `land`, `oneWay`, carried rider, camera follow, multi-touch controls, and the same actions driven by a gamepad | [Platformer](recipes-action.md#platformer) |
13
13
  | `volley.js` | Restitution ball, `land`, collision response, coarse AI timer | [Ball sports](recipes-action.md#ball-sports--breakout-physics) |
14
14
  | `racing.js` | `carMode`, drift, skid marks, checkpoints and lap state | [Top-down racer](recipes-action.md#top-down-racer) |
15
15
  | `cards.js` | Dealing, fanned layout, selection tweens and idle wobble | [Cards and board games](recipes-adventure-effects.md#cards-and-board-games) |
@@ -18,6 +18,7 @@ The demos prove that API combinations run together on the current module. They a
18
18
  | `skate.js` | Endless-runner pooling, carried/uncarried terrain, parallax, emitter and audio hooks | [Endless runner](recipes-action.md#endless-runner) |
19
19
  | `pointclick.js` | Tap-to-walk with `findPath` → `followPath`, hotspots and `ySort` | [Point-and-click](recipes-adventure-effects.md#point--click-adventure) |
20
20
  | `particles.js` | Continuous/burst emitters, sprite target, HUD telemetry | [Particles](recipes-adventure-effects.md#particles) and [debugging](debugging-performance.md) |
21
+ | `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) |
21
22
  | `rhythm.js` | Pooled notes, native velocity, `press` pads, timing and audio feedback | [Rhythm game](recipes-action.md#rhythm-game) |
22
23
  | `camera.js` | Two-axis follow, bounds, zoom, shake, camera effects, parallax and debug HUD | [Camera work](recipes-adventure-effects.md#camera-work) |
23
24
  | `rope.js` | Rope pinned to a sprite, fixed head, tail weight | [Ropes and chains](recipes-adventure-effects.md#ropes-and-chains) |
@@ -34,9 +35,9 @@ The demos prove that API combinations run together on the current module. They a
34
35
  | `timescale.js` | Slow/frozen scene, engine timer versus real-time interval | [Game-clock timers](patterns.md#timers-on-the-game-clock) |
35
36
  | `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) |
36
37
  | `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) |
37
- | `plinko.js` | Circular pegs, swept balls and trigger slots | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
38
- | `pool.js` | Bilateral circle momentum, rails, pockets and linear damping | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
39
- | `drum.js` | Analytic circular containment and contact-driven impulses | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
38
+ | `plinko.js` | Circular pegs, swept balls, trigger slots and `solidimpact` audio | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
39
+ | `pool.js` | Bilateral circle momentum, rails, pockets, linear damping and `solidimpact` | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
40
+ | `drum.js` | Analytic circular containment, contact-driven impulses and `solidimpact` | [Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs) |
40
41
  | `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) |
41
42
  | `tilemap.js` | One 120×90 `TileLayer`, visible-cell rendering, cell collision, live edits, A* and HUD | [Tile maps](tilemaps.md) |
42
43
 
@@ -1,6 +1,6 @@
1
1
  # Cross-cutting ti.game patterns
2
2
 
3
- Verified against `ti.game@c216e7f` and the runnable examples under `example/`. These are production-oriented extractions of the API combinations demonstrated upstream; they intentionally omit demo-launcher and back-navigation plumbing.
3
+ Verified against `ti.game@3bea2f4` and the runnable examples under `example/`. These are production-oriented extractions of the API combinations demonstrated upstream; they intentionally omit demo-launcher and back-navigation plumbing.
4
4
 
5
5
  See [examples.md](examples.md) to choose the source demo, [api.md](api.md) for exact members, and the genre recipe files for complete game loops.
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Setting up a ti.game project
2
2
 
3
- Installing the module, wiring it into Classic and Alloy projects, organizing art and sound, and the lifecycle work the engine does not do for you. Verified against `ti.game@c216e7f` (2026-08-28).
3
+ Installing the module, wiring it into Classic and Alloy projects, organizing art and sound, and the lifecycle work the engine does not do for you. Verified against `ti.game@3bea2f4` (2026-09-02).
4
4
 
5
5
  <!-- TOC-START -->
6
6
  ## Contents
@@ -44,7 +44,7 @@ Then declare it in `tiapp.xml`. The iOS platform key is `iphone`, not `ios`:
44
44
 
45
45
  Pinning `version` is worth the extra attribute: with two module versions installed side by side, an unpinned entry silently picks the highest one.
46
46
 
47
- **The version string does not identify a feature set — the build date does.** The manifest went `0.3.0` → **`0.4.0`** on 2026-08-20 and **`0.5.0`** on 2026-08-27, and in between each number kept accepting features without moving. So every one of them covers several incompatible builds:
47
+ **The version string does not identify a feature set — the build date does.** The manifest went `0.3.0` → **`0.4.0`** on 2026-08-20, **`0.5.0`** on 2026-08-27 and **`0.6.0`** on 2026-09-02, and in between each number kept accepting features without moving. So every one of them covers several incompatible builds:
48
48
 
49
49
  | A build from | Missing, compared to what this skill documents |
50
50
  | --- | --- |
@@ -61,8 +61,12 @@ Pinning `version` is worth the extra attribute: with two module versions install
61
61
  | `0.5.0` built before `85a723d` | additionally read-only `onWallLeft` / `onWallRight` and the `wallhit` transition event for sprite and TileLayer walls |
62
62
  | `0.5.0` built before `05cb60c` | additionally `wallSlideSpeed`, the native downward-speed cap while pressed against a wall |
63
63
  | `0.5.0` built before `c216e7f` | on Android only, string-row TileLayers with a nested JS `legend` decode as empty because the proxy rejects the ordinary map object; numeric data is unaffected |
64
+ | `0.5.0` built before `d7d471e` (2026-08-28) | additionally the whole gamepad surface: `buttondown`/`buttonup`, `stick`, `trigger`, `gamepadconnected`/`gamepaddisconnected`, and the `gamepads`/`gamepad`/dead-zone properties |
65
+ | `0.5.0` built before `af2e544` (2026-08-29) | additionally circular horizontal worlds: `gameView.worldWrapX` and per-sprite `wrapWorldX` |
66
+ | `0.5.0` built before `80d8f2b` (2026-08-30) | additionally the `solidimpact` event and `impactThreshold` |
67
+ | `0.5.0` built before `9c45cf6` (2026-09-02) — **this includes the artifacts most people have** | no crash fixes: writing `y`/`zIndex` from JS can abort the GL thread with *Comparison method violates its general contract*; a sound whose load failed throws `IllegalStateException` on the next `play()`; passing a mistyped value to `sheet`/`target`/`head`/`tail`/`font` aborts the process in JNI instead of raising a JS error. Also `pinch`/`rotate` fire per motion event instead of ~10 Hz, two-finger rotation can add a whole turn at the ±180° flip, the tap/drag slop ignores `cameraScale`, `cameraX`/`cameraY`/`cameraScale`/`cameraBounds`/`throttle`/`steering` are dropped when passed to a `create*` call, a removed sprite stays retained as the camera's follow target, `collidesWith`/`solidWith` reject a bare string, and the iOS glitch shader goes NaN after roughly 27 minutes |
64
68
 
65
- The newest local release tag is still `0.4.0` (2026-08-20), so tagged artifacts lag one manifest bump plus the HUD and TileLayer work that landed after `0.5.0` was introduced. If a documented call is `undefined`, check the manifest and build commit before hunting for a typo, and rebuild from upstream `main`. More reliable than either is feature detection: read a property **before** writing it (`typeof sprite.hitboxScaleX === 'undefined'`) or probe a method/factory (`typeof Game.createTileLayer === 'function'`). Write-then-read is not proof because Kroll can retain an unknown property on the proxy.
69
+ The newest upstream release is `0.5.0` (2026-09-02), and for once the tag is ahead of the local habit rather than behind it: it sits on `9c45cf6`, so the published zip carries every fix in the row above, while a module built locally from source any time in the previous two weeks reports the same `0.5.0` and does not. Same number, different engine — prefer the release artifact, or rebuild from upstream `main` and let the manifest read `0.6.0`. If a documented call is `undefined`, check the manifest and build commit before hunting for a typo, and rebuild from upstream `main`. More reliable than either is feature detection: read a property **before** writing it (`typeof sprite.hitboxScaleX === 'undefined'`) or probe a method/factory (`typeof Game.createTileLayer === 'function'`). Write-then-read is not proof because Kroll can retain an unknown property on the proxy.
66
70
 
67
71
  Building the module from source:
68
72
 
@@ -1,6 +1,6 @@
1
1
  # Action-game recipes
2
2
 
3
- Production-oriented patterns distilled from the upstream action demos at `ti.game@c216e7f`. Use [examples.md](examples.md) to locate the full runnable demo and [api.md](api.md) for exact defaults and payloads. The wall-contact API is source/README-backed because upstream has not added a wall-jump demo yet.
3
+ Production-oriented patterns distilled from the upstream action demos at `ti.game@3bea2f4`. Use [examples.md](examples.md) to locate the full runnable demo and [api.md](api.md) for exact defaults and payloads. The wall-contact API is source/README-backed because upstream has not added a wall-jump demo yet.
4
4
 
5
5
  <!-- TOC-START -->
6
6
  ## Contents
@@ -1,6 +1,6 @@
1
1
  # Adventure, presentation, and effects recipes
2
2
 
3
- Production-oriented patterns distilled from the upstream non-action demos at `ti.game@c216e7f`. Use [examples.md](examples.md) to locate the full runnable demo and [api.md](api.md) for exact defaults and payloads.
3
+ Production-oriented patterns distilled from the upstream non-action demos at `ti.game@3bea2f4`. Use [examples.md](examples.md) to locate the full runnable demo and [api.md](api.md) for exact defaults and payloads.
4
4
 
5
5
  <!-- TOC-START -->
6
6
  ## Contents
@@ -1,6 +1,6 @@
1
1
  # Missing, partial, and out-of-scope features
2
2
 
3
- Verified against `ti.game@c216e7f` (2026-08-28), especially upstream `TODO.md` and both native implementations. Planned is not shipped; partial means the exact boundary below matters.
3
+ Verified against `ti.game@3bea2f4` (2026-09-02), especially upstream `TODO.md` and both native implementations. Planned is not shipped; partial means the exact boundary below matters.
4
4
 
5
5
  ## Not available today
6
6
 
@@ -8,12 +8,13 @@ Verified against `ti.game@c216e7f` (2026-08-28), especially upstream `TODO.md` a
8
8
  | --- | --- | --- |
9
9
  | `gameView.panTo(x, y, options)` | Planned | Tween an invisible sprite, follow it for the cutscene, then follow the player again |
10
10
  | Full `sprite.parent` transform inheritance | Partial | `attachTo()` supplies position, optional rotation, and inherited opacity. Scale, visibility, flips, tint, hit-test hierarchy, y-sort hierarchy, and a `parent` property do not exist |
11
- | Native joystick/d-pad binding | Planned | Use overlaid Titanium controls that write native motion/input properties on `touchstart`/`touchend`; sibling views support simultaneous touches |
12
- | Gamepad support | Planned | Touch input only unless the app adds its own native integration |
11
+ | Native joystick/d-pad binding (`joystick.bind(sprite)`) | Planned | Use overlaid Titanium controls that write native motion/input properties on `touchstart`/`touchend`; sibling views support simultaneous touches. Upstream intends the same binding to accept a gamepad's left stick, which also does not exist yet |
13
12
  | Complete platformer slopes | Partial | `rotatedRect` geometry, normals, rider carrying, and sliding work. Smooth uphill walking, contact-only surface friction, and connected terrain are not implemented |
14
13
  | Tile animation | Planned TileLayer follow-up | Change ids deliberately or use sprites for the animated cells; there is no per-id native frame cycle |
15
14
  | `raycast()` against TileLayer cells | Planned TileLayer follow-up | Use sprite colliders for ray-visible obstacles or grid/cell queries appropriate to the game |
16
15
  | Tile trigger events (`collision`/`collisionend` for water/lava) | Planned TileLayer follow-up | Use sparse invisible trigger sprites or map the mover/world point to cells on a coarse decision/event path |
16
+ | `solidimpact` from TileLayer cells | Not implemented | Solid cells block, bounce and ground movers but emit no impact event. Put a sprite solid where the hit must be audible |
17
+ | Periodic repetition of `raycast()`, `findPath()`, particles, ropes and skid trails across a `worldWrapX` seam | Not implemented | Those queries and effects see a single linear world. Keep them away from the seam, or resolve the nearest image in JS before calling |
17
18
  | One-call Tiled loader for tilesets and multiple layers | Planned TileLayer follow-up | Parse the exported JSON in app code and create one TileLayer per layer/tileset |
18
19
  | Per-frame animation events | Planned | Split an action into short non-looping animations and react to `animationcomplete`, or use an engine timer aligned to a deliberate cadence |
19
20
  | Ping-pong, repeat/yoyo, random animation offset, per-sprite animation speed | Planned | Author ping-pong frames or re-launch a tween from `complete`; avoid a JS per-frame loop |
@@ -35,6 +36,9 @@ Verified against `ti.game@c216e7f` (2026-08-28), especially upstream `TODO.md` a
35
36
  - Shape-aware solids: circle and rotated-rect geometry, one-way platforms, carried riders, containment, bilateral circle push, two-sided restitution, `gravityX`, and `linearDamping`.
36
37
  - Native wall contacts: `onWallLeft` / `onWallRight`, `wallhit`, TileLayer-wall parity, and `wallSlideSpeed`.
37
38
  - Native particles, ropes, effect/music sound backends, car physics, thrust, drag/pinch/rotate, and multi-touch sprites.
39
+ - Gamepads: normalized `buttondown`/`buttonup` names, throttled `stick`/`trigger`, connect/disconnect events, `gamepads`/`gamepad` snapshots, dead zone and stick-press hysteresis, and release-everything on background or disconnect.
40
+ - Circular horizontal worlds: `gameView.worldWrapX` plus per-sprite `wrapWorldX`, seam-aware camera, rendering, touch, overlap, solid resolution and swept movement, and a full-width TileLayer that repeats across the seam.
41
+ - Discrete physical impacts: the `solidimpact` event with per-receiver `impactThreshold`, compensated closing speed, shared contact point, opposite normals and mixed restitution, for `block`, `contain` and bilateral `push` responses between sprites.
38
42
 
39
43
  ## Deliberately out of scope upstream
40
44
 
@@ -45,6 +49,6 @@ Verified against `ti.game@c216e7f` (2026-08-28), especially upstream `TODO.md` a
45
49
 
46
50
  ## Checking a moving target
47
51
 
48
- The manifest remains `0.5.0` across shape-aware solids, the HUD, and TileLayer, so the version cannot prove a feature exists. Prefer the build commit/date and safe feature probes such as `typeof Game.createTileLayer === 'function'`. Read a property before writing it; Kroll can preserve unknown writes on a proxy.
52
+ The manifest number cannot prove a feature exists: it stayed at `0.5.0` across shape-aware solids, the HUD, TileLayer, gamepads, world wrapping and `solidimpact`, and only moved to `0.6.0` in the commit right after the `0.5.0` release was tagged. A locally built artifact stamped `0.5.0` may therefore be weeks older than the published release of the same number. Prefer the build commit/date and safe feature probes such as `typeof Game.createTileLayer === 'function'`. Read a property before writing it; Kroll can preserve unknown writes on a proxy.
49
53
 
50
54
  Before claiming an item is still absent, re-check upstream `TODO.md`, `README.md`, both platform proxies, and the examples. Source code wins over prose when they disagree.
@@ -1,6 +1,6 @@
1
1
  # Native tile maps
2
2
 
3
- Verified against `ti.game@c216e7f`: upstream `README.md` § Tile maps, `example/tilemap.js`, both `TileLayerProxy` implementations, both native `TileLayer` engines, scene collision, and `Pathfinder`.
3
+ Verified against `ti.game@3bea2f4`: upstream `README.md` § Tile maps, `example/tilemap.js`, both `TileLayerProxy` implementations, both native `TileLayer` engines, scene collision, and `Pathfinder`.
4
4
 
5
5
  Use `Game.createTileLayer()` for a large or mostly static grid. The engine stores the whole map but renders only cells inside the camera, batches the layer through one sheet, checks only collision cells under a mover, and feeds solid cells into `findPath`. A small map whose tiles need independent sprite behavior can still use sprites; `topdown.js` is a 16×12 example of that older pattern.
6
6