@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.
Files changed (81) hide show
  1. package/CONTRIBUTING.md +82 -0
  2. package/README.md +83 -72
  3. package/agents/ti-pro.md +1 -1
  4. package/commands/ti-audit.md +2 -2
  5. package/docs/examples.md +91 -0
  6. package/docs/knowledge-index.md +57 -0
  7. package/lib/commands/uninstall.js +19 -16
  8. package/lib/commands/update.js +1 -1
  9. package/lib/downloader.js +1 -1
  10. package/lib/utils.js +2 -0
  11. package/package.json +4 -2
  12. package/skills/purgetss/references/multi-density-images.md +1 -1
  13. package/skills/ti-admob-unified/SKILL.md +21 -7
  14. package/skills/ti-admob-unified/assets/example/app.js +229 -25
  15. package/skills/ti-admob-unified/assets/example/lib/ads.js +30 -2
  16. package/skills/ti-admob-unified/assets/example/library-usage.js +33 -1
  17. package/skills/ti-admob-unified/references/api-reference.md +39 -16
  18. package/skills/ti-admob-unified/references/banners.md +9 -9
  19. package/skills/ti-admob-unified/references/contributing.md +14 -9
  20. package/skills/ti-admob-unified/references/dependencies.md +31 -25
  21. package/skills/ti-admob-unified/references/fullscreen.md +43 -28
  22. package/skills/ti-admob-unified/references/game-monetization.md +138 -7
  23. package/skills/ti-admob-unified/references/getting-started.md +75 -31
  24. package/skills/ti-admob-unified/references/index.md +5 -2
  25. package/skills/ti-admob-unified/references/integration.md +8 -6
  26. package/skills/ti-admob-unified/references/library.md +41 -10
  27. package/skills/ti-admob-unified/references/mediation-parity.md +17 -0
  28. package/skills/ti-admob-unified/references/mediation.md +22 -9
  29. package/skills/ti-admob-unified/references/migration.md +11 -7
  30. package/skills/ti-admob-unified/references/module-changelog.md +41 -11
  31. package/skills/ti-admob-unified/references/privacy.md +35 -8
  32. package/skills/ti-admob-unified/references/production.md +67 -0
  33. package/skills/ti-admob-unified/references/rewarded-interstitial.md +101 -0
  34. package/skills/ti-admob-unified/references/roadmap.md +28 -31
  35. package/skills/ti-admob-unified/references/source-notes.md +51 -4
  36. package/skills/ti-admob-unified/references/source-snapshot.json +249 -45
  37. package/skills/ti-admob-unified/references/third-party-notices.md +4 -4
  38. package/skills/ti-game/SKILL.md +23 -6
  39. package/skills/ti-game/references/api.md +34 -30
  40. package/skills/ti-game/references/debugging-performance.md +18 -9
  41. package/skills/ti-game/references/examples.md +103 -20
  42. package/skills/ti-game/references/input.md +101 -0
  43. package/skills/ti-game/references/patterns.md +39 -15
  44. package/skills/ti-game/references/physics.md +80 -52
  45. package/skills/ti-game/references/project-setup.md +118 -56
  46. package/skills/ti-game/references/recipes-action.md +107 -8
  47. package/skills/ti-game/references/recipes-adventure-effects.md +41 -9
  48. package/skills/ti-game/references/roadmap.md +10 -3
  49. package/skills/ti-game/references/source-notes.md +81 -0
  50. package/skills/ti-game/references/tilemaps.md +12 -4
  51. package/skills/ti-synthengine/SKILL.md +81 -95
  52. package/skills/ti-synthengine/assets/example/README.md +84 -0
  53. package/skills/ti-synthengine/assets/example/app.js +1178 -0
  54. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +83 -0
  55. package/skills/ti-synthengine/assets/example/lib/sequenceScore.js +25 -0
  56. package/skills/ti-synthengine/references/advanced-examples.md +174 -0
  57. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  58. package/skills/ti-synthengine/references/android-sim80-correction.json +162 -0
  59. package/skills/ti-synthengine/references/api.md +434 -380
  60. package/skills/ti-synthengine/references/audio-engine.md +157 -230
  61. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  62. package/skills/ti-synthengine/references/examples.md +123 -253
  63. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  64. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  65. package/skills/ti-synthengine/references/frontier-physical-validation.json +1918 -0
  66. package/skills/ti-synthengine/references/guide.md +188 -156
  67. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  68. package/skills/ti-synthengine/references/performance-review.json +1146 -0
  69. package/skills/ti-synthengine/references/physical-validation.json +1457 -0
  70. package/skills/ti-synthengine/references/recipes-interaction.md +549 -0
  71. package/skills/ti-synthengine/references/recipes.md +423 -599
  72. package/skills/ti-synthengine/references/recommendations.md +63 -113
  73. package/skills/ti-synthengine/references/sound-design.md +134 -258
  74. package/skills/ti-synthengine/references/source-notes.md +93 -105
  75. package/skills/ti-synthengine/references/source-snapshot.json +379 -32
  76. package/skills/ti-synthengine/references/stats-performance.md +64 -20
  77. package/skills/ti-synthengine/references/studio-validation.json +6202 -0
  78. package/skills/ti-synthengine/references/troubleshooting.md +109 -121
  79. package/skills/ti-synthengine/references/unified-bypass-performance.json +22425 -0
  80. package/skills/ti-synthengine/references/unified-validation.json +314 -0
  81. package/AGENTS-VERCEL-RESEARCH.md +0 -175
@@ -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@3bea2f4` (2026-09-02).
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. Reviewed against `macCesar/ti.game@2bf5f82` (2026-10-23), based on `m1ga/ti.game@88e181c`. See [source-notes.md](source-notes.md) for provenance and verification limits.
4
4
 
5
5
  <!-- TOC-START -->
6
6
  ## Contents
@@ -14,6 +14,8 @@ Installing the module, wiring it into Classic and Alloy projects, organizing art
14
14
  - [Lifecycle and cleanup](#lifecycle-and-cleanup)
15
15
  - [Debugging and tuning](#debugging-and-tuning)
16
16
  - [Platform differences](#platform-differences)
17
+ - [Window resizing, focus and app configuration](#window-resizing-focus-and-app-configuration)
18
+ - [Apple diagnostic workflows](#apple-diagnostic-workflows)
17
19
 
18
20
  <!-- TOC-END -->
19
21
 
@@ -25,8 +27,8 @@ There is no registry and no `ti module install` command — Titanium modules are
25
27
 
26
28
  ```
27
29
  modules/
28
- ├── android/ti.game/0.5.0/
29
- └── iphone/ti.game/0.5.0/
30
+ ├── android/ti.game/1.0.0/
31
+ └── iphone/ti.game/1.0.0/
30
32
  ```
31
33
 
32
34
  Unzipping the module zip at the project root creates exactly that layout.
@@ -37,48 +39,53 @@ Then declare it in `tiapp.xml`. The iOS platform key is `iphone`, not `ios`:
37
39
 
38
40
  ```xml
39
41
  <modules>
40
- <module platform="android" version="0.5.0">ti.game</module>
41
- <module platform="iphone" version="0.5.0">ti.game</module>
42
+ <module platform="android" version="1.0.0">ti.game</module>
43
+ <module platform="iphone" version="1.0.0">ti.game</module>
42
44
  </modules>
43
45
  ```
44
46
 
45
47
  Pinning `version` is worth the extra attribute: with two module versions installed side by side, an unpinned entry silently picks the highest one.
46
48
 
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:
49
+ ### Requirements and source builds
48
50
 
49
- | A build from | Missing, compared to what this skill documents |
50
- | --- | --- |
51
- | `0.3.0` before 2026-08-18 | `createText`, `createFont`, `screenFixed`, `swept`, `collisionend` |
52
- | `0.3.0` before 2026-08-19 | also `followPath`, `play(name, { then })`, `raycast`, `after`/`every`, `scrollFactor`, `multiply`/`screen` blends |
53
- | `0.3.0` before 2026-08-19 16:00 UTC+2 | additionally bleeds at grid-frame edges on smoothed sheets |
54
- | `0.4.0` — including the 2026-08-20 release zip | `hitboxScaleX`/`hitboxScaleY`, text `maxWidth`, `SpriteSheet.unload()`/`Font.unload()` and the LiveView renderer lifecycle, all added on 2026-08-23 with the manifest untouched |
55
- | `0.4.0` built before 2026-08-24 | additionally rocks side to side through a multi-frame animation on a `smoothing: true` grid sheet — the half-texel inset was one-sided, so the end frames came out slightly wider and off-centre |
56
- | `0.4.0` built before 2026-08-26 | additionally `sprite.attachTo()` / `detach()` / `attachedTo`, and `gameView.follow()` still aborts the app on Android if it is handed anything that is not a sprite |
57
- | `0.4.0` built on 2026-08-26 but before `83b7863` | additionally the opacity an attachment inherits from its target, the `anchor` property, and percentage strings on every ratio — a build in this window has `attachTo` but treats `'55%'` as a number it cannot read |
58
- | `0.4.0` at its last commit (`b780051`) | additionally everything the `0.5.0` bump carries: `hitboxShape: 'rotatedRect'`, circular solids resolved as circles, `solidMode`, `gravityX`, `linearDamping`, and `restitution` read off both sides of a contact. It also still pulls a swept sprite back to where its frame started when it begins the frame already touching a solid, so a `swept: true` body parked on a slope creeps and then breaks loose as if launched |
59
- | `0.5.0` built before `d042060` | the performance HUD object form (`debug: { hud, hitbox, hudFont }`) and the `performance` event |
60
- | `0.5.0` built before `10a046e` | additionally `Game.createTileLayer()`, native visible-cell tile rendering, tile collision/live edits, and TileLayer participation in `findPath` |
61
- | `0.5.0` built before `85a723d` | additionally read-only `onWallLeft` / `onWallRight` and the `wallhit` transition event for sprite and TileLayer walls |
62
- | `0.5.0` built before `05cb60c` | additionally `wallSlideSpeed`, the native downward-speed cap while pressed against a wall |
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 |
68
-
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.
70
-
71
- Building the module from source:
72
-
73
- ```bash
74
- cd android
75
- ti build -p android --build-only # → android/dist/ti.game-android-<version>.zip
76
- ti build -p ios --build-only # macOS only → ios/dist/ti.game-iphone-<version>.zip
51
+ Both manifests declare version `1.0.0` and minimum Titanium SDK `13.4.0`.
52
+ The documented build SDK is `13.4.1.GA`. Android uses OpenGL ES 2.0.
53
+ Apple uses Metal and requires iOS/iPadOS 15 or later; the package contains
54
+ arm64 devices, arm64/x86_64 Simulator and arm64/x86_64 Mac Catalyst slices.
55
+ Architecture presence does not establish execution on that hardware.
56
+
57
+ From the module repository root:
58
+
59
+ ```sh
60
+ (cd android && ti build -p android --build-only --sdk 13.4.1.GA)
61
+ (cd ios && ti build -p ios --build-only --sdk 13.4.1.GA)
77
62
  ```
78
63
 
64
+ Apple requires macOS/Xcode. Its `ios/titanium.xcconfig` selects
65
+ `13.4.1.GA`; selecting another SDK in the CLI also requires changing that
66
+ Xcode SDK setting or providing an external override:
67
+
68
+ ```sh
69
+ cat > /tmp/ti-game-sdk.xcconfig <<'CONFIG'
70
+ TITANIUM_SDK_VERSION = 13.4.1.GA
71
+ CONFIG
72
+ XCODE_XCCONFIG_FILE=/tmp/ti-game-sdk.xcconfig ti build -p ios --build-only --sdk 13.4.1.GA
73
+ ```
74
+
75
+ Run the latter command inside `ios/`. Applications need no `default.metallib`:
76
+ shaders are embedded and compiled once during device initialization. A native
77
+ initialization error logs a diagnostic and stops the view; there is no public
78
+ backend selector or production Apple OpenGL fallback.
79
+
80
+ Upstream's nightly is an automated `main` build, not a stable release. Record
81
+ source commit and ZIP SHA-256 for any installed artifact: version `1.0.0`
82
+ alone does not establish the reviewed fork's feature set. Probe a method before
83
+ calling it; read a property before writing it. Unknown Kroll properties can
84
+ round-trip without native support, so write-then-read is not proof.
85
+
79
86
  ## Classic projects
80
87
 
81
- `require('ti.game')` anywhere and add the GameView to a window. The 34 demos in the module's `example/` folder are Classic, each one a CommonJS module exporting a start function (`app.js` is the launcher and `backnav.js` is a helper, neither is a demo):
88
+ `require('ti.game')` anywhere and add the GameView to a window. The launcher has 34 mobile touch demos and a 35th Mac input demo; the demos in the module's `example/` folder are Classic, each one a CommonJS module exporting a start function (`app.js` is the launcher; `backnav.js`, `keyboard.js`, and `pool-viewport.js` are helpers):
82
89
 
83
90
  ```javascript
84
91
  // Resources/flappy.js
@@ -111,8 +118,8 @@ The standard Alloy element for a native module view. `method` names the factory
111
118
  ```xml
112
119
  <!-- app/views/index.xml -->
113
120
  <Alloy>
114
- <Window id="index" navBarHidden="true" exitOnClose="true" onOpen="onOpen" onClose="onClose">
115
- <Module id="gameView" module="ti.game" method="createGameView" backgroundColor="#8ed8f8" />
121
+ <Window id="index" navBarHidden="true" exitOnClose="true" onClose="onClose">
122
+ <Module id="gameView" module="ti.game" method="createGameView" backgroundColor="#8ed8f8" onResize="onResize" />
116
123
  <Button id="jumpButton" title="JUMP" onTouchstart="jump" />
117
124
  </Window>
118
125
  </Alloy>
@@ -126,10 +133,6 @@ const heroSheet = Game.createSpriteSheet({ image: 'hero.png', frameWidth: 64, fr
126
133
 
127
134
  let initialized = false;
128
135
 
129
- function onOpen() {
130
- $.gameView.addEventListener('resize', onResize);
131
- }
132
-
133
136
  function onResize(e) {
134
137
  if (initialized) {
135
138
  return;
@@ -270,11 +273,17 @@ const mono = Game.createFont({ image: 'fonts/mono.png', charWidth: 9, charHeight
270
273
 
271
274
  These are **bitmap** fonts — do not put them in `app/assets/fonts/` expecting Titanium's native font handling, and do not use a `.ttf` here. To go from a TTF to either format, the module ships `tools/genfont.py`, which rasterizes it into a grid or BMFont atlas.
272
275
 
273
- The built-in font covers ASCII 32..126 only. Any game whose text includes `ñ`, accents or `¿` needs a generated font — or a `Ti.UI.Label` overlay for that specific string.
276
+ The built-in Press Start 2P font covers ASCII 32..126 plus
277
+ `ÁÉÍÓÚÜÑáéíóúüñ¿¡ÄÖäößËÏëïÈèÊêÀà`. Use precomposed characters;
278
+ combining accents and capital `ẞ` are unsupported (`SS` is the documented
279
+ uppercase alternative). Custom grid fonts still default to ASCII. Generate
280
+ a custom font for other characters. The README's older Draw text paragraph
281
+ still says ASCII-only; its current Font contract and both embedded native
282
+ character maps establish this expanded repertoire.
274
283
 
275
284
  ## Lifecycle and cleanup
276
285
 
277
- The engine handles what it owns: the render loop pauses and resumes with the activity or app, music pauses and resumes with it, and textures survive an EGL context loss. **Everything you created in JS is yours to stop.**
286
+ The engine handles what it owns: the render loop pauses and resumes with the activity or app, music pauses and resumes with it, and Android textures survive an EGL context loss. Apple retains in-flight GPU resources until Metal commands complete. **Everything you created in JS is yours to stop.**
278
287
 
279
288
  A leaked `setInterval` keeps running after the window closes, keeps reading sprite properties from a dead scene and keeps the whole closure alive. A looping `createSound` keeps playing over the next screen.
280
289
 
@@ -319,7 +328,7 @@ function listen(target, eventName, handler) {
319
328
 
320
329
  ### GPU textures: `unload()`
321
330
 
322
- Sheets and fonts hold GL textures, and until 2026-08-23 nothing but losing the context ever freed one. `spriteSheet.unload()` and `font.unload()` release the texture on the next rendered frame, from the render thread. Releasing the proxy does the same, so a sheet that goes out of scope no longer strands its memory.
331
+ Sheets and fonts hold GPU textures. `spriteSheet.unload()` and `font.unload()` schedule texture disposal on the render thread; Apple retains resources already encoded in pending Metal commands until completion. Releasing the proxy does the same, so a sheet that goes out of scope no longer strands its memory.
323
332
 
324
333
  This is for **level streaming** — a game that loads world 2's atlases while world 1's are still resident. It is not a cache: the unload is permanent, and any sprite still pointing at that sheet stops drawing.
325
334
 
@@ -343,16 +352,17 @@ alive, keep that GameView attached or create a fresh one on return; rebuilding
343
352
  only its Sprite session is insufficient. Verify the actual UIKit presentation
344
353
  and installed module on-device.
345
354
 
346
- Multiple retained iOS GameViews also share each physical GCController's
347
- single `extendedGamepad.valueChangedHandler`. At this pin, each view's
348
- TGGamepadController installs its own handler and detach clears it. Another
349
- view can therefore replace or clear an active scene's receiver. Use explicit
350
- input ownership and test open/close transitions; do not assume independent
351
- gamepad subscriptions or treat a local fork fix as shipped upstream.
355
+ The reviewed Apple controller implementation uses a shared dispatcher with
356
+ weak per-view receivers (`TGAttachGamepadDevice` / `TGDetachGamepadDevice`).
357
+ Closing one receiver no longer clears another view's physical controller
358
+ handler. Define gameplay input ownership explicitly: retained views may still
359
+ observe gamepad events. Focused keyboard/actions have separate ownership rules
360
+ in [input.md](input.md).
352
361
 
353
- Sources: upstream `ios/Classes/TiGameGameView.m` (`didMoveToWindow`,
354
- `shutdownRendering`) and `ios/Classes/TGGamepadController.m`
355
- (`attachController`, `detachController`) at `3bea2f4`.
362
+ Sources: `ios/Classes/TiGameGameView.m` (`didMoveToWindow`,
363
+ `shutdownRendering`) and `ios/Classes/TGGamepadController.m` at the reviewed
364
+ pin. Native inspection verifies lifecycle contracts, not a specific navigation
365
+ presentation on a device.
356
366
 
357
367
  ### LiveView reloads
358
368
 
@@ -373,14 +383,66 @@ Since 2026-08-23 the module retires the previous generation itself: stale render
373
383
 
374
384
  ## Platform differences
375
385
 
376
- The JS API is identical on Android and iOS. What differs is around it:
386
+ The public API is mostly shared. Preserve these platform contracts:
377
387
 
378
388
  - **`Ti.UI.createNotification` (Android Toast) does not exist on iOS.** The demos branch on `Ti.Platform.osname` and fall back to `Ti.UI.createAlertDialog`.
379
- - **The iOS Simulator renders at a 1x logical drawable** (its translated OpenGL path is disproportionately expensive at 3x Retina) and is noticeably slower than any real device. Real iPhone and iPad builds keep the device's native screen scale. Judge performance on device.
389
+ - **The iOS Simulator renders at a 1x logical drawable** (the Metal port retains this policy for comparisons). Simulator timing is not a controlled comparison with physical hardware. Real iPhone and iPad builds keep the device's native screen scale. Judge performance on device.
380
390
  - **`logicalDensityFactor` lies in the iOS Simulator** — it reports the device scale while the surface renders at 1x. Derive dp→scene units from the surface instead: `H / Ti.Platform.displayCaps.platformHeight`.
381
391
  - **Android's `touchFeedback` ripple** cannot animate over the GL canvas and logs `RippleDrawable` errors; give on-screen buttons manual `backgroundColor` feedback on `touchstart`/`touchend`.
382
392
  - **Sound formats**: WAV, MP3 and OGG on Android; WAV, MP3 and M4A on iOS. WAV for effects, MP3 for music covers both.
383
393
  - **TileLayer grid configuration**: assign `legend`, `firstGid`, `cols`, and `rows` in `createTileLayer()`. iOS accepts later writes; Android treats them as creation-time inputs.
384
394
  - **Android `legend` compatibility**: string-row maps require build `c216e7f` or newer. Older Android proxies ignore a normal nested JS legend object; use numeric data or update the module.
385
- - **Performance telemetry**: `averagePresentMs` and `presentFailures` exist only on iOS and are omitted on Android.
395
+ - **Performance telemetry**: `averagePresentMs` and `presentFailures` exist only on Apple and are omitted on Android. They measure CPU drawable acquisition/submission and drawable/command failures, not GPU time or confirmed physical presentation.
386
396
  - **`theme: 'Theme.Titanium.DayNight.NoTitleBar'`** on the window is the Android idiom for a fullscreen game surface; on iOS use `navBarHidden`/`statusBarHidden` (or the Alloy XML equivalents).
397
+
398
+ ## Window resizing, focus and app configuration
399
+
400
+ The guarded first `resize` creates the level once. It does not make that level
401
+ responsive: handle later dimensions to reposition HUD/controls and update
402
+ bounds without resetting gameplay. Native touch points are surface pixels;
403
+ Titanium overlay dimensions are points/dp. Derive their conversion from the
404
+ actual canvas, including safe-area and window changes. Pool's source helper
405
+ uses a centered 380×680 point portrait stage; other demos generally use the
406
+ full available canvas. See [examples.md](examples.md).
407
+
408
+ `extendSafeArea` changes whether window children draw behind system bars.
409
+ For keyboard/mouse, configure the consuming app's indirect-input plist and
410
+ focus ownership before testing; see [input.md](input.md). A fullscreen theme
411
+ alone does not configure launch orientation or guarantee hidden system bars.
412
+
413
+ Orientation and launch-screen policy are Titanium app configuration, absent
414
+ from the module tutorial. Use `ti-ui` and `ti-guides` before adding a fixed
415
+ orientation: check the app's `tiapp.xml`, iPhone/iPad supported orientations,
416
+ launch screen, root window and applicable Android activities before opening
417
+ the first game window. Do not treat the older `ti-ui` orientation summary as
418
+ proof of complete multi-activity or current launch-screen configuration; that
419
+ cross-skill audit remains outside this module refresh.
420
+
421
+ ## Apple diagnostic workflows
422
+
423
+ From the source checkout, `python3 tests/apple-renderer/run.py` builds native
424
+ renderer XCTest fixtures (Xcode and XcodeGen required). Its default target is
425
+ iPhone 16e/iOS 18.6; choose another installed runtime with `--destination` and
426
+ `--deployment-target`. Catalyst uses
427
+ `--destination 'platform=macOS,variant=Mac Catalyst' --deployment-target 15.0`.
428
+ `--baseline` compares the preserved OpenGL baseline `127a08c`; this does not
429
+ add an OpenGL backend to the module. Device execution needs
430
+ `--signing-profile`, `--signing-team` and `--signing-identity` together.
431
+ Outputs remain under `/tmp` by default.
432
+
433
+ After building the Apple ZIP, generate an isolated Classic smoke-test app:
434
+
435
+ ```sh
436
+ python3 tests/apple-renderer/prepare_demos.py --icon /path/to/DefaultIcon.png
437
+ python3 tests/apple-renderer/prepare_demos.py --icon /path/to/DefaultIcon.png --include-input --output /tmp/ti-game-mac-demos
438
+ python3 tests/apple-renderer/test_prepare_demos.py
439
+ ```
440
+
441
+ The helper discovers launcher demos, defaults to SDK `13.4.1.GA` (`--sdk`
442
+ overrides), enables indirect pointer events, and writes diagnostics to
443
+ `metal-demos.json` in the generated app's data directory. Native fixture and
444
+ smoke checks do not replace visual/gameplay review. Current upstream evidence
445
+ records 18 renderer cases on Apple Silicon Catalyst and a physical iPad;
446
+ physical iPhone, Intel execution, complete interactive acceptance and controlled
447
+ performance comparisons remain pending. This skill audit does not rerun or
448
+ extend that hardware evidence.
@@ -1,6 +1,6 @@
1
1
  # Action-game recipes
2
2
 
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.
3
+ Production-oriented patterns distilled from the action demos and public contracts at `macCesar/ti.game@2bf5f82` (documentation checkout above upstream `m1ga/ti.game@88e181c`; previous skill pin `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
  Use [physics.md](physics.md) for the implemented equations, units, jump tuning,
6
6
  inertia versus direct control, and numerical limits. Recipes select native
@@ -9,6 +9,7 @@ mechanisms; they do not replace that model reference.
9
9
  <!-- TOC-START -->
10
10
  ## Contents
11
11
 
12
+ - [Shared digital actions](#shared-digital-actions)
12
13
  - [Tap-to-flap](#tap-to-flap)
13
14
  - [Platformer](#platformer)
14
15
  - [Top-down / Zelda](#top-down--zelda)
@@ -21,6 +22,80 @@ mechanisms; they do not replace that model reference.
21
22
 
22
23
  <!-- TOC-END -->
23
24
 
25
+ ## Shared digital actions
26
+
27
+ The current `inputmap.js` demonstrates movement, grounded jumps, normalized
28
+ straight/diagonal shots, grounded crouching with fixed feet, separate
29
+ instructions/feedback, and native text-field ownership. [The catalog](examples.md#keyboard-shortcuts-and-shared-helpers)
30
+ lists every shortcut and the Mac-only right/middle mouse limitation. Its
31
+ last-pressed-direction policy differs deliberately from Platformer/Volley,
32
+ where opposite directions cancel and releasing one resumes the other.
33
+
34
+ For keyboard and digital gamepad actions, write native velocity on action
35
+ transitions; choose one gameplay route per action. This curated extraction
36
+ assumes `player`, `RUN_SPEED`, `jump()` and `win` already exist:
37
+
38
+ ```javascript
39
+ const held = { left: false, right: false };
40
+ gameView.inputMap = {
41
+ gameplay: {
42
+ left: [{ device: 'keyboard', code: 'ArrowLeft' }, { device: 'gamepad', button: 'left' }],
43
+ right: [{ device: 'keyboard', code: 'ArrowRight' }, { device: 'gamepad', button: 'right' }],
44
+ jump: [{ device: 'keyboard', code: 'Space' }, { device: 'gamepad', button: 'a' }]
45
+ }
46
+ };
47
+ function applyDirection() {
48
+ player.velocityX = (Number(held.right) - Number(held.left)) * RUN_SPEED;
49
+ }
50
+ function down(e) {
51
+ if (Object.prototype.hasOwnProperty.call(held, e.action)) {
52
+ held[e.action] = true;
53
+ applyDirection();
54
+ } else if (e.action === 'jump') {
55
+ jump();
56
+ }
57
+ }
58
+ function up(e) {
59
+ if (Object.prototype.hasOwnProperty.call(held, e.action)) {
60
+ held[e.action] = false;
61
+ applyDirection();
62
+ }
63
+ }
64
+ function focus() {
65
+ gameView.inputContext = 'gameplay';
66
+ gameView.focusInput();
67
+ }
68
+ function release() {
69
+ gameView.inputContext = null;
70
+ held.left = held.right = false;
71
+ applyDirection();
72
+ }
73
+ gameView.addEventListener('actiondown', down);
74
+ gameView.addEventListener('actionup', up);
75
+ win.addEventListener('open', focus);
76
+ win.addEventListener('focus', focus);
77
+ win.addEventListener('blur', release);
78
+ win.addEventListener('close', () => {
79
+ release();
80
+ gameView.removeEventListener('actiondown', down);
81
+ gameView.removeEventListener('actionup', up);
82
+ });
83
+ ```
84
+
85
+ Native actions aggregate all bindings: releasing one key/controller leaves
86
+ the action held until the last source releases; repeat does not retrigger it.
87
+ A context change releases actions and requires a fresh press. Do not route
88
+ mapped pad buttons through individual button listeners as well. When keeping
89
+ touch buttons, track each touch source separately from the aggregated native
90
+ action, as Platformer/Volley do, so a key release cannot cancel a held touch.
91
+
92
+ With a text field, disable the action context on field focus and clear held
93
+ state; restore it and call `focusInput()` only on explicit canvas/focus-button
94
+ return. A paused context can retain just Escape to resume, as in the demo;
95
+ `timeScale = 0` alone does not disable input. Track/cancel owned game timers
96
+ on close. [Project setup](project-setup.md) owns platform and plist prerequisites;
97
+ [API](api.md) owns map validation, raw events and focus contracts.
98
+
24
99
  ## Tap-to-flap
25
100
 
26
101
  Gravity plus a replacement vertical velocity per tap, not an accumulated impulse. Scoring and death are collisions with invisible zones. The rotation tween is what sells the arc.
@@ -68,7 +143,8 @@ pig.addEventListener('collision', (e) => {
68
143
  });
69
144
  ```
70
145
 
71
- Obstacles are pooled gates: three sprites each (top pipe, bottom pipe, invisible score zone) that scroll left on `velocityX` and get repositioned when they leave the screen. Freezing the game means setting every scroller's `velocityX = 0` and the player's `gravity = 0` — the engine has no global "stop", but `gameView.timeScale = 0` is one if you want the scene frozen mid-flight.
146
+ Obstacles are pooled gates: three sprites each (top pipe, bottom pipe, invisible score zone) that scroll left on `velocityX` and get repositioned when they leave the screen. The demo wins after five gates; a collision with a pipe or ground ends the
147
+ run, and the next press resets. Freezing the game means setting every scroller's `velocityX = 0` and the player's `gravity = 0` — the engine has no global "stop", but `gameView.timeScale = 0` is one if you want the scene frozen mid-flight.
72
148
 
73
149
  ## Platformer
74
150
 
@@ -176,7 +252,10 @@ Set `carryRiders: false` on terrain that scrolls under a player who should stay
176
252
 
177
253
  ## Top-down / Zelda
178
254
 
179
- A tile map from a string array, plus `ySort` for depth. Only water is solid; trees are pure decoration you can walk behind.
255
+ A tile map from a string array, plus `ySort` for depth. Water tiles and the separate house block movement; trees are nonblocking
256
+ decoration you can walk behind. The abbreviated map below shows the tile
257
+ portion; add the house as a separate `collisionGroup: 'solid'`, `ySort: true`
258
+ sprite at the actors' `zIndex`, as in the demo.
180
259
 
181
260
  ```javascript
182
261
  const MAP = [
@@ -204,7 +283,7 @@ for (let row = 0; row < ROWS; row++) {
204
283
  width: TILE,
205
284
  height: TILE,
206
285
  zIndex: 0,
207
- collisionGroup: (kind === 'W') ? 'solid' : null // only water blocks
286
+ collisionGroup: (kind === 'W') ? 'solid' : null // only water tiles block; house is a separate solid
208
287
  }));
209
288
  }
210
289
  }
@@ -283,6 +362,18 @@ timer = setInterval(() => {
283
362
  }, 100);
284
363
  ```
285
364
 
365
+ The full companion AI in `topdown.js` adds a timed state machine to the
366
+ simple follow extraction above: sit while the player is idle, stand while
367
+ near a moving player, and walk when outside follow distance. After a startup
368
+ grace period it may approach a nearby tree, face the trunk and play `pee`
369
+ for 1800 ms, then wait 12000 ms before another visit. If the player leaves
370
+ more than five tiles away while it approaches, cancel that visit and use a
371
+ 4000 ms retry delay. A started `pee` finishes before following resumes.
372
+ Its 100 ms JS decision interval is cleared on window close; native velocity
373
+ and animation still perform all motion. Water tiles also play staggered
374
+ `flow` animations so the shore does not ripple in sync; track any startup
375
+ JS timeouts when adapting the demo for repeated screen entry.
376
+
286
377
  ## Top-down racer
287
378
 
288
379
  `carMode` gives you throttle, steering and emergent drift. You supply the track and the lap logic.
@@ -420,11 +511,16 @@ warnTimer = setInterval(() => {
420
511
  ```
421
512
 
422
513
  Rocks are round (`hitboxShape: 'circle'`), drift on random velocities, spin on `angularVelocity`, and are recycled by `visible = false` rather than removed.
514
+ The demo starts five rocks; clearing them starts another wave. The ship keeps
515
+ flying after a hit, loses one of three lives and receives brief invulnerability.
516
+ Its last life blinks red, and zero lives resets the game. The nose-fired pooled
517
+ bolts inherit ship velocity; hold-to-fire only schedules shots, never movement.
518
+ Stop the looping thruster sound on release and every owned sound/timer on close.
423
519
 
424
520
  ## Lunar lander
425
521
 
426
522
  Same flight model as Asteroids, with gravity and a touchdown rule. This is a
427
- curated recipe, not one of the 34 upstream demos. `land` reports grounding
523
+ curated recipe, not one of the 35 module demos. `land` reports grounding
428
524
  after the solid response: live velocity may already be zero or reflected.
429
525
  For a static sprite pad, use the snapshotted normal speed in `solidimpact`.
430
526
  It is compensated for this frame's acceleration, not the full incoming
@@ -622,9 +718,8 @@ function hitLane(lane, pad) {
622
718
  An invisible `misszone` trigger below the pads catches gems that got away and
623
719
  counts the miss. `FALL_SPEED = (PAD_Y + NOTE) / 1.6` gives a 1.6 s trip from
624
720
  `-NOTE` to `PAD_Y`. At the stated 150 BPM, `60000 / 150 = 400` ms is a quarter
625
- note; 1.6 s is four quarter notes (eight eighths). The upstream comments say
626
- "eighth" but the timing constants describe quarters. Correct that wording,
627
- not the engine or the demo's timing without a separate design decision.
721
+ note; 1.6 s is four quarter notes (eight eighths). The current source corrects the older "eighth" comments
722
+ while preserving the timing constants; an old copy of the demo may still have that wording.
628
723
  Position windows convert to time with `windowSeconds = windowPx / FALL_SPEED`.
629
724
  The demo uses a JS interval and independent music playback, not an audio-clock
630
725
  beat scheduler; see [rhythm timing](physics.md#rhythm-timing).
@@ -660,6 +755,10 @@ ball.addEventListener('collision', (e) => {
660
755
  });
661
756
  ```
662
757
 
758
+ In Volley, touching the floor scores for the opposite side and serves from
759
+ the center toward the side that conceded. The player/opponent stay on their
760
+ own sides of the net; the AI follows the ball on its side before jumping.
761
+
663
762
  Note the split: walls and the net are `solidWith` (the engine bounces the ball off them for free), while the players and the floor are `collidesWith` (you decide what happens). For breakout, bricks go in `collidesWith` and are removed with `visible = false` on hit while the paddle and walls stay solid.
664
763
 
665
764
  A `restitution: 1` ball reflects larger impacts without restitution loss;
@@ -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@3bea2f4`. 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 non-action demos and public contracts at `macCesar/ti.game@2bf5f82` (documentation checkout above upstream `m1ga/ti.game@88e181c`; previous skill pin `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
@@ -52,6 +52,9 @@ sprite.addEventListener('complete', () => {
52
52
 
53
53
  ```javascript
54
54
  sprite.addEventListener('tap', () => {
55
+ if (card.state !== 'hand' || card.played || played) {
56
+ return;
57
+ }
55
58
  if (card.selected) {
56
59
  card.selected = false;
57
60
  sprite.animate({ x: home.x, y: home.y, rotation: home.rotation, scale: 1, glowOpacity: 0, duration: 150, easing: Game.EASE_OUT });
@@ -65,16 +68,34 @@ sprite.addEventListener('tap', () => {
65
68
  });
66
69
  ```
67
70
 
71
+ The full card state machine deals five cards, permits at most three selected,
72
+ and accepts Done only after dealing/sweeping ends and exactly three are
73
+ selected. `played` guards the whole hand; `card.played` guards each card.
74
+ Done moves the three selected cards side-by-side, disables their wobble and
75
+ fades their glows while the two remaining cards re-fan at center. It becomes
76
+ Reset; reset marks cards `leaving`, disables wobble, sweeps all five left,
77
+ then redeals after 750 ms. Track/cancel that redeal timeout and the 350 ms
78
+ opening-deal timeout when adapting the demo. The completion listener should
79
+ restore wobble after any tween only for cards still in `hand` and unplayed.
80
+
68
81
  **Drag & drop with snapping** (puzzle pieces, board tiles). The drag itself is native; you only decide where it lands:
69
82
 
70
83
  ```javascript
71
84
  const piece = Game.createSprite({ sheet: sheet, frame: index, x: homeX, y: homeY, width: PIECE, height: PIECE, draggable: true, zIndex: 10 });
85
+ let currentCell = null;
72
86
 
73
87
  piece.addEventListener('press', () => {
74
88
  piece.zIndex = 100; // draw above everything while held
75
89
  piece.animate({ scale: 1.15, duration: 120, easing: Game.EASE_OUT });
76
90
  });
77
91
 
92
+ piece.addEventListener('dragstart', () => {
93
+ if (currentCell) {
94
+ currentCell.piece = null;
95
+ currentCell = null;
96
+ }
97
+ });
98
+
78
99
  piece.addEventListener('release', () => {
79
100
  piece.animate({ scale: 1, duration: 120, easing: Game.EASE_OUT });
80
101
  });
@@ -95,6 +116,7 @@ piece.addEventListener('dragend', (e) => {
95
116
  piece.zIndex = 10;
96
117
  if (best) {
97
118
  best.piece = piece;
119
+ currentCell = best;
98
120
  piece.animate({ x: best.x, y: best.y, duration: 150, easing: Game.EASE_OUT });
99
121
  } else {
100
122
  piece.animate({ x: homeX, y: homeY, duration: 400, easing: Game.EASE_IN_OUT });
@@ -102,7 +124,13 @@ piece.addEventListener('dragend', (e) => {
102
124
  });
103
125
  ```
104
126
 
105
- Because every handler is per sprite, this is multi-touch for free: each finger grabs its own piece.
127
+ Because every handler is per sprite, each finger can grab its own piece.
128
+ Release the previous cell on `dragstart` before searching for a new free cell.
129
+ The full puzzle checks all four cells after snapping; when filled, it gives
130
+ each piece a staggered 360-degree celebration tween. Restart clears every
131
+ piece's `currentCell`/occupancy, reduces rotation modulo 360 to avoid unwinding
132
+ previous celebration turns, then tweens position/rotation/scale home. Keep
133
+ those state transitions when building a reusable board.
106
134
 
107
135
  ## Point & click adventure
108
136
 
@@ -233,9 +261,10 @@ Add `blend: 'add'` for fire, magic and sparks — overlaps bloom instead of cove
233
261
  ## Ropes and chains
234
262
 
235
263
  Rope `damping` retains a fraction of the previous displacement **per update**,
236
- unlike Sprite `linearDamping`, whose coefficient is in 1/s. In the pinned
237
- source, Verlet displacement and constraint relaxation still run at `dt=0`;
238
- `timeScale=0` does not guarantee a motionless rope. See the equations and
264
+ unlike Sprite `linearDamping`, whose coefficient is in 1/s. The current
265
+ Android and Apple updates return at `dt <= 0`: nodes, previous positions,
266
+ constraints and tether response freeze. A newly created rope still initializes
267
+ its drawable geometry, and changing the segment count can rebuild it while paused. See the equations and
239
268
  pause boundary in [physics.md](physics.md#verlet-ropes-and-tethers).
240
269
 
241
270
  ```javascript
@@ -294,7 +323,10 @@ gameView.cameraEffect = 'glitch';
294
323
  gameView.cameraEffect = 'none'; // skips the pass entirely
295
324
  ```
296
325
 
297
- For a big ground plane, one `tileRepeat` sprite covering the whole world beats a grid of tile sprites — the texture keeps its native density and the scene stays one draw call.
326
+ For a repeated decorative ground plane, use `tileRepeat` on one large sprite
327
+ instead of a grid of decorative sprites; it repeats the texture at native
328
+ density. For maps with individual solid/editable cells, use [TileLayer](tilemaps.md).
329
+ Batching, blend changes and effects determine actual draw-call counts.
298
330
 
299
331
  ## Visual effects
300
332
 
@@ -343,9 +375,9 @@ gameView.addEventListener('tap', () => {
343
375
 
344
376
  **`timeScale`** scales the dt for physics, sheet animations, tweens, particles,
345
377
  camera and game-clock timers, while rendering/touch keep running. `0.5` gives
346
- slow motion; `0` gives zero dt for a pause or hit-stop. Rope still applies
347
- previous displacement and constraints at zero dt in this snapshot, so test
348
- rope scenes separately. `timescale.js` compares a game-clock timer with an
378
+ slow motion; `0` gives zero dt for a pause or hit-stop, including rope
379
+ integration and tether response in this snapshot. Rendering, touch and direct
380
+ app property writes remain active; pausing is not an input lock. `timescale.js` compares a game-clock timer with an
349
381
  independent JS interval; it does not include a rope.
350
382
 
351
383
  **`idleAnimation`** — a per-sprite organic sway that composes with tweens and drags. Cheap life for cards, icons, floating pickups and menu art. Disable it before a tween that must land precisely.
@@ -1,6 +1,7 @@
1
1
  # Missing, partial, and out-of-scope features
2
2
 
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.
3
+ Reviewed on 2026-10-23 against upstream native `88e181c` and
4
+ `macCesar/ti.game@2bf5f82`, especially `TODO.md` and both native implementations. Planned is not shipped; partial means the exact boundary below matters.
4
5
 
5
6
  ## Not available today
6
7
 
@@ -22,7 +23,7 @@ Verified against `ti.game@3bea2f4` (2026-09-02), especially upstream `TODO.md` a
22
23
  | TypeScript declarations | Planned | No upstream `ti.game.d.ts` exists |
23
24
  | Aseprite JSON import | Planned | SpriteSheet atlases support TexturePacker JSON hash/array formats |
24
25
  | Tweening `width`, `height`, or `tintColor` | Not implemented | Tween scale/opacity/glowOpacity; other properties are instant writes |
25
- | A GameView-level `stop()` | Already covered differently | `timeScale = 0` passes zero dt while rendering/touch continue; Rope can still integrate prior displacement and constraints. `pause()` stops the render loop; see [physics.md](physics.md#verlet-ropes-and-tethers) |
26
+ | A GameView-level `stop()` | Already covered differently | `timeScale = 0` freezes scene time, including Rope integration/constraints/tether, while rendering/touch continue. `pause()` stops the render loop; see [physics.md](physics.md#verlet-ropes-and-tethers) |
26
27
  | Standard Titanium touch events on GameView | By design | Use GameView `press`/`tap`/`release`, sprite events, or Titanium views over the canvas |
27
28
  | A per-frame JS event | Deliberately absent | React to discrete events and use native movement; coarse `every()` calls are for decisions, not motion |
28
29
 
@@ -35,7 +36,13 @@ Verified against `ti.game@3bea2f4` (2026-09-02), especially upstream `TODO.md` a
35
36
  - Sprite paths, animation chaining, raycasts, A*, game-clock timers, collision exit, swept movers, and four blend modes.
36
37
  - Shape-aware solids: circle and rotated-rect geometry, one-way platforms, carried riders, containment, bilateral circle push, two-sided restitution, `gravityX`, and `linearDamping`.
37
38
  - Native wall contacts: `onWallLeft` / `onWallRight`, `wallhit`, TileLayer-wall parity, and `wallSlideSpeed`.
38
- - Native particles, ropes, effect/music sound backends, car physics, thrust, drag/pinch/rotate, and multi-touch sprites.
39
+ - Native particles, ropes, effect/music sound backends, car physics, thrust, drag/pinch/rotate, and multi-touch sprites. Rope freezes node/history/constraint/tether updates at zero dt; fresh geometry can initialize while paused.
40
+ - Damped powered launch and post-contact rest: effective thrust/gravity/car input survives the 4 px/s cutoff; coasting and blocked tangential drift still settle.
41
+ - Circle-aware swept overlap events (circle/circle and rounded circle/rect/OBB), relative target movement, and periodic offsets; translation only with final-frame shape size/orientation.
42
+ - Resting sweep tangential sliding and periodic path frame-delta preservation.
43
+ - Configurable native digital keyboard/mouse/gamepad actions with contexts, aggregate press/release, raw key events and canvas focus. Mac right/middle mouse action delivery remains unavailable in the reviewed docs; see [input.md](input.md).
44
+ - Extended built-in pixel glyphs for Spanish, German, Dutch and selected French characters; custom font generation still defaults to ASCII.
45
+ - Apple Metal renderer on iPhone, iPad and Mac Catalyst; Android retains OpenGL ES 2.0. This is backend support, not proof of installed-binary/device acceptance.
39
46
  - 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
47
  - 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
48
  - 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.