@maccesar/titools 5.1.1 → 5.2.1

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 (98) hide show
  1. package/CONTRIBUTING.md +69 -0
  2. package/README.md +112 -72
  3. package/agents/ti-pro.md +5 -1
  4. package/commands/ti-audit.md +5 -1
  5. package/docs/examples.md +91 -0
  6. package/docs/knowledge-index.md +57 -0
  7. package/lib/config.js +2 -0
  8. package/lib/downloader.js +1 -1
  9. package/package.json +4 -2
  10. package/skills/purgetss/references/multi-density-images.md +1 -1
  11. package/skills/ti-admob-unified/SKILL.md +58 -0
  12. package/skills/ti-admob-unified/agents/openai.yaml +4 -0
  13. package/skills/ti-admob-unified/assets/example/app.js +1207 -0
  14. package/skills/ti-admob-unified/assets/example/lib/ads.js +646 -0
  15. package/skills/ti-admob-unified/assets/example/library-usage.js +136 -0
  16. package/skills/ti-admob-unified/references/api-reference.md +615 -0
  17. package/skills/ti-admob-unified/references/banners.md +435 -0
  18. package/skills/ti-admob-unified/references/contributing.md +233 -0
  19. package/skills/ti-admob-unified/references/dependencies.md +91 -0
  20. package/skills/ti-admob-unified/references/fullscreen.md +238 -0
  21. package/skills/ti-admob-unified/references/game-monetization.md +148 -0
  22. package/skills/ti-admob-unified/references/getting-started.md +319 -0
  23. package/skills/ti-admob-unified/references/index.md +33 -0
  24. package/skills/ti-admob-unified/references/integration.md +87 -0
  25. package/skills/ti-admob-unified/references/library.md +220 -0
  26. package/skills/ti-admob-unified/references/mediation-parity.md +17 -0
  27. package/skills/ti-admob-unified/references/mediation.md +145 -0
  28. package/skills/ti-admob-unified/references/migration.md +259 -0
  29. package/skills/ti-admob-unified/references/module-changelog.md +84 -0
  30. package/skills/ti-admob-unified/references/module-license.txt +21 -0
  31. package/skills/ti-admob-unified/references/privacy.md +281 -0
  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 +143 -0
  35. package/skills/ti-admob-unified/references/source-notes.md +80 -0
  36. package/skills/ti-admob-unified/references/source-snapshot.json +644 -0
  37. package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
  38. package/skills/ti-game/SKILL.md +31 -5
  39. package/skills/ti-game/references/api.md +36 -24
  40. package/skills/ti-game/references/debugging-performance.md +22 -7
  41. package/skills/ti-game/references/examples.md +113 -20
  42. package/skills/ti-game/references/input.md +101 -0
  43. package/skills/ti-game/references/patterns.md +56 -10
  44. package/skills/ti-game/references/physics.md +530 -0
  45. package/skills/ti-game/references/project-setup.md +131 -47
  46. package/skills/ti-game/references/recipes-action.md +149 -18
  47. package/skills/ti-game/references/recipes-adventure-effects.md +47 -4
  48. package/skills/ti-game/references/roadmap.md +11 -4
  49. package/skills/ti-game/references/source-notes.md +81 -0
  50. package/skills/ti-game/references/tilemaps.md +13 -4
  51. package/skills/ti-game-assets/SKILL.md +53 -0
  52. package/skills/ti-game-assets/agents/openai.yaml +4 -0
  53. package/skills/ti-game-assets/references/alpha.md +26 -0
  54. package/skills/ti-game-assets/references/art-direction.md +37 -0
  55. package/skills/ti-game-assets/references/integration.md +49 -0
  56. package/skills/ti-game-assets/references/manifest.example.json +429 -0
  57. package/skills/ti-game-assets/references/manifest.md +39 -0
  58. package/skills/ti-game-assets/references/offline-audio.md +33 -0
  59. package/skills/ti-game-assets/references/prompts.md +33 -0
  60. package/skills/ti-game-assets/references/repeating-textures.md +17 -0
  61. package/skills/ti-game-assets/references/responsible-ads.md +31 -0
  62. package/skills/ti-game-assets/references/sound-assets.md +42 -0
  63. package/skills/ti-game-assets/references/vector-sources.md +21 -0
  64. package/skills/ti-game-assets/scripts/alpha_tools.py +160 -0
  65. package/skills/ti-game-assets/scripts/requirements.txt +1 -0
  66. package/skills/ti-game-assets/scripts/validate_manifest.py +267 -0
  67. package/skills/ti-synthengine/SKILL.md +77 -67
  68. package/skills/ti-synthengine/assets/example/README.md +84 -0
  69. package/skills/ti-synthengine/assets/example/app.js +1178 -0
  70. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +83 -0
  71. package/skills/ti-synthengine/assets/example/lib/sequenceScore.js +25 -0
  72. package/skills/ti-synthengine/references/advanced-examples.md +174 -0
  73. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  74. package/skills/ti-synthengine/references/android-sim80-correction.json +162 -0
  75. package/skills/ti-synthengine/references/api.md +573 -153
  76. package/skills/ti-synthengine/references/audio-engine.md +272 -124
  77. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  78. package/skills/ti-synthengine/references/examples.md +355 -106
  79. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  80. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  81. package/skills/ti-synthengine/references/frontier-physical-validation.json +1918 -0
  82. package/skills/ti-synthengine/references/guide.md +426 -75
  83. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  84. package/skills/ti-synthengine/references/performance-review.json +1146 -0
  85. package/skills/ti-synthengine/references/physical-validation.json +1457 -0
  86. package/skills/ti-synthengine/references/recipes-interaction.md +549 -0
  87. package/skills/ti-synthengine/references/recipes.md +434 -420
  88. package/skills/ti-synthengine/references/recommendations.md +72 -85
  89. package/skills/ti-synthengine/references/sound-design.md +375 -155
  90. package/skills/ti-synthengine/references/source-notes.md +109 -0
  91. package/skills/ti-synthengine/references/source-snapshot.json +452 -0
  92. package/skills/ti-synthengine/references/stats-performance.json +2992 -0
  93. package/skills/ti-synthengine/references/stats-performance.md +82 -0
  94. package/skills/ti-synthengine/references/studio-validation.json +6202 -0
  95. package/skills/ti-synthengine/references/troubleshooting.md +123 -78
  96. package/skills/ti-synthengine/references/unified-bypass-performance.json +22425 -0
  97. package/skills/ti-synthengine/references/unified-validation.json +314 -0
  98. package/AGENTS-VERCEL-RESEARCH.md +0 -175
@@ -0,0 +1,101 @@
1
+ # Keyboard, mouse and action maps
2
+
3
+ Reviewed 2026-10-23 against `m1ga/ti.game@88e181cb85526d8146d0be6e09b2b90c2551baad` native input resolvers and canvas adapters, with README clarification from `macCesar/ti.game@2bf5f828a3bb8028eed20d6ba27b97dd3aa9a753`. Requires an artifact containing those APIs; a shared version number is insufficient evidence. Existing controller events and analog input remain documented in [api.md](api.md#gamepads).
4
+
5
+ ## Map and event contract
6
+
7
+ `GameView.inputMap` is optional, live, readable/writable and defaults to `{}`. It maps contexts to named actions, each with an array of digital bindings. `inputContext` accepts only a string or `null` (other types throw) and defaults to `'gameplay'`; `null` disables actions. Replace the whole map to rebind; the module supplies no settings UI or persistence.
8
+
9
+ ```javascript
10
+ const gameView = Game.createGameView({
11
+ inputMap: {
12
+ gameplay: {
13
+ thrust: [
14
+ { device: 'keyboard', code: 'KeyW' },
15
+ { device: 'keyboard', code: 'ArrowUp' }
16
+ ],
17
+ fire: [
18
+ { device: 'keyboard', code: 'Space' },
19
+ { device: 'mouse', button: 'left' },
20
+ { device: 'gamepad', button: 'a' }
21
+ ],
22
+ pause: [{ device: 'keyboard', code: 'Escape' }]
23
+ },
24
+ paused: { pause: [{ device: 'keyboard', code: 'Escape' }] }
25
+ }
26
+ });
27
+ gameView.addEventListener('actiondown', ({ action, context }) => {
28
+ // Dispatch discrete actions or write native motion properties here.
29
+ // A pause handler can change inputContext to 'paused' and timeScale to 0.
30
+ });
31
+ gameView.addEventListener('actionup', ({ action, context }) => {
32
+ // Clear held controls, including releases caused by lost focus/cancellation.
33
+ });
34
+ // Add the view, open the window, then call this (also after native text entry):
35
+ gameView.focusInput();
36
+ ```
37
+
38
+ The snippet is a map/transition template; scene setup and window lifecycle are in [project-setup.md](project-setup.md). No per-frame JavaScript polling is needed: action resolution runs natively on input transitions.
39
+
40
+ | Input | Contract |
41
+ | --- | --- |
42
+ | `actiondown` / `actionup` | Payload `{ action, context }`. An action goes down on its first active binding and up after its last source releases, across different bindings and controllers |
43
+ | Map replacement | A valid map releases previous active actions and requires a fresh press; held keys cannot activate the replacement map until released and pressed again |
44
+ | Context change | Releases active actions from the old context, requires a fresh press; assigning the same context leaves state unchanged |
45
+ | Invalid map | Throws without replacing the map or active state |
46
+ | Cancellation | Focus loss, suspension, device disconnection and view shutdown release held actions. An action release need not have a physical/raw `keyup`; drive gameplay from action transitions |
47
+ | Auto-repeat | Ignored as a new press; holding a key does not repeat `actiondown` |
48
+
49
+ Context and action names must be nonempty strings. Empty contexts and empty action arrays are allowed. Each binding has **exactly two keys**: `device` plus `code` for keyboard, or `device` plus `button` for mouse/gamepad. Analog axes are not action bindings.
50
+
51
+ | Device | Accepted binding values |
52
+ | --- | --- |
53
+ | `keyboard` (`code`) | `KeyA`…`KeyZ`, `Digit0`…`Digit9`, `ArrowUp`, `ArrowDown`, `ArrowLeft`, `ArrowRight`, `Space`, `Escape`, `Enter`, `Tab`, `Backspace`, `Delete`, `Home`, `End`, `PageUp`, `PageDown`, `F1`…`F12`, `ShiftLeft`/`ShiftRight`, `ControlLeft`/`ControlRight`, `AltLeft`/`AltRight`, `MetaLeft`/`MetaRight` |
54
+ | `mouse` (`button`) | `left`, `middle`, `right` |
55
+ | `gamepad` (`button`) | `a`, `b`, `x`, `y`, `l1`, `r1`, `l2`, `r2`, `l3`, `r3`, `start`, `select`, `home`, `up`, `down`, `left`, `right` |
56
+
57
+ Existing gamepad properties, individual button events, sticks and triggers are unchanged. Use mapped actions **or** individual button handlers for each gameplay action to avoid dispatching twice. An app keeping its own gamepad adapter can omit gamepad bindings from the map.
58
+
59
+ ## Keyboard focus and pointer ownership
60
+
61
+ Raw `keydown` / `keyup` events carry `{ code, character, modifiers: { shift, control, alt, meta } }`. `code` identifies the physical key independently of its text character; `character` can be empty for non-text keys.
62
+
63
+ Only the focused GameView receives keyboard events. `focusInput()` requests canvas focus on the UI thread; it neither opens a software keyboard nor captures keys globally. Native text fields retain their focus. Call it after opening the game window and after returning from a native text field.
64
+
65
+ When the active context has mouse bindings, pointer gestures delivered to the canvas feed mapped actions and are consumed rather than also becoming sprite tap/drag interactions. Empty maps, keyboard-only contexts and disabled actions preserve existing sprite pointer interaction. Overlay Titanium controls retain their clicks and focus; the module does not capture their mouse events. Android retains the engine touch listener.
66
+
67
+ For iOS/iPadOS 13.4–16 pointer identification, set the consuming application's `tiapp.xml` opt-in (iOS 17+ defaults it to true):
68
+
69
+ ```xml
70
+ <ios>
71
+ <plist>
72
+ <dict>
73
+ <key>UIApplicationSupportsIndirectInputEvents</key>
74
+ <true/>
75
+ </dict>
76
+ </plist>
77
+ </ios>
78
+ ```
79
+
80
+ This application-level setting distinguishes indirect pointers from direct touch; the module does not change global input settings at runtime. The current Apple package targets iOS/iPadOS **15+** and Mac Catalyst with Metal. Its keyboard/pointer guards dating from iOS 13.4 and disconnection notification guards dating from iOS 14 do not lower that package minimum.
81
+
82
+ ## Runnable example and acceptance limits
83
+
84
+ `example/inputmap.js` is the current action-map demo. The launcher exposes it on Mac only; mobile builds retain touch-ready demos. The source remains usable for external-peripheral testing on mobile.
85
+
86
+ - A/D, J/L, arrows or gamepad move the character. The last pressed direction wins; releasing it resumes the other held direction.
87
+ - Space or gamepad A jumps. W/I/Up aims upward, including diagonal combinations; S/K/Down crouches.
88
+ - Left canvas click or gamepad X fires. Right/middle mouse bindings are accepted for testing but currently do not trigger actions on Mac; left click is verified by the source's acceptance notes.
89
+ - Escape switches pause/resume action contexts. A separate monitor shows raw key codes/modifiers and existing gamepad buttons, sticks and triggers.
90
+ - Instructions and action feedback occupy separate areas. An overlaid text field and focus button demonstrate native ownership; clicking the canvas after text entry restores game focus, and the first click does not fire. The shared back control returns to the launcher.
91
+
92
+ Upstream verification commands, run from the module checkout:
93
+
94
+ ```sh
95
+ python3 tests/input-map/run.py
96
+ python3 tests/input-map/uikit.py
97
+ python3 tests/gamepad-fixture/check.py
98
+ node --test tests/example-input/controls.test.js
99
+ ```
100
+
101
+ Resolver regressions test native binding validation, aggregation and transitions. UIKit fixtures exercise production responder methods with renderer/event sinks for focus, raw-key repetition/cancellation and pointer/overlay ownership. GameController fixtures cover receiver fanout, disconnect and action delivery without individual listeners. JavaScript example tests check demo control behavior. Compilation and these fixtures do not establish physical keyboard, mouse, controller or overlay behavior on every supported device; record that acceptance separately.
@@ -1,6 +1,7 @@
1
1
  # Cross-cutting ti.game patterns
2
2
 
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.
3
+ Reviewed against upstream native `88e181c` and the documentation/examples in
4
+ `macCesar/ti.game@2bf5f82` on 2026-10-23. These are production-oriented extractions of the API combinations demonstrated upstream; they intentionally omit demo-launcher and back-navigation plumbing.
4
5
 
5
6
  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
7
 
@@ -76,7 +77,14 @@ module.exports = function () {
76
77
  };
77
78
  ```
78
79
 
79
- Sizing everything as a fraction of `W`/`H` is not cosmetic — a jump tuned in absolute pixels becomes unplayable on a different device. The demos consistently express speeds as `W * f`, accelerations as `H * f` and sprite sizes as `Math.min(W, H) * f`.
80
+ Use the actual canvas for sizing. Current demos cap foreground art and text by
81
+ height as well as width so short desktop windows do not clip their controls.
82
+ For example, text can use `Math.max(1, Math.round(Math.min(W / 240, H / 360)))`.
83
+ Measure surface pixels per view point from `W / gameView.rect.width` when
84
+ converting native control margins; use a positive finite density fallback.
85
+ Keep a guarded first `resize` for world creation and relayout the HUD on later
86
+ resizes when the app supports changing windows. See
87
+ [project-setup.md](project-setup.md) for complete integration.
80
88
 
81
89
  ## Cross-cutting patterns
82
90
 
@@ -146,7 +154,7 @@ Recycling a sprite with `visible = false` fires `collisionend` on anything it wa
146
154
 
147
155
  ### Freeing a level's textures
148
156
 
149
- Pooling recycles sprites; it does not free the **texture** behind them. A game that streams worlds accumulates every atlas it has ever loaded until the GL context dies. `unload()` releases one on the next rendered frame.
157
+ Pooling recycles sprites; it does not free the **texture** behind them. A game that streams worlds accumulates every atlas it has ever loaded until scene resources are released. `unload()` releases one on the next rendered frame.
150
158
 
151
159
  ```javascript
152
160
  function unloadLevel(level) {
@@ -206,7 +214,17 @@ Slower layers read as further away. Three layers (clouds at 0.15×, hills at 0.4
206
214
 
207
215
  ### Multitouch on-screen controls
208
216
 
209
- Each button must be **its own Titanium view** — sibling views receive simultaneous pointers, so holding a direction while pressing jump works. A single container splitting touches itself does not.
217
+ Use separate sibling input targets for held directions and actions. Their
218
+ native control type also matters for simultaneous touch; the factory below
219
+ uses Label rather than UIButton-backed controls.
220
+
221
+ The factory below deliberately uses Label. In Titanium SDK13.4.1.GA on iOS,
222
+ TiUIButton sets its internal UIButton's `exclusiveTouch=YES`; changing the
223
+ outer view's multitouch flag does not remove that exclusivity. Keep held
224
+ flight controls as sibling Label/View targets or native game sprites and
225
+ verify simultaneous input on-device. See `iphone/Classes/TiUIButton.m`,
226
+ method `button`, in that SDK; this is a Titanium UI boundary, not a ti.game
227
+ physics discrepancy.
210
228
 
211
229
  ```javascript
212
230
  function makeButton(title, position, fontSize) {
@@ -252,7 +270,14 @@ bindHold(leftButton, () => {
252
270
  });
253
271
  ```
254
272
 
255
- The guard in the release handler is what keeps two opposite buttons from fighting: releasing left only zeroes steering if left is still the active direction.
273
+ The guard avoids releasing one control cancelling a newer opposite command.
274
+ It does not restore a still-held older direction. For mixed touch, keyboard and
275
+ controller input, track each source's held state and derive direction from the
276
+ active set, as current `platformer.js`/`volley.js` do. They cancel opposing
277
+ directions and resume the remaining one on release. `inputmap.js` deliberately
278
+ uses a different last-pressed-wins rule. Choose the policy explicitly; do not
279
+ confuse either with the native action aggregator, which combines bindings for
280
+ one named action. See [input.md](input.md) for native input maps and focus rules.
256
281
 
257
282
  ### HUD — `text.js`, `flappy.js`
258
283
 
@@ -306,7 +331,11 @@ dialog.addEventListener('tap', () => {
306
331
 
307
332
  `maxWidth` is measured before `scale`, which is the only trap here — pass screen pixels straight in and the block comes out `scale` times too wide. `align` positions the lines against each other (the block's own width), not inside the wrap column, so anchor the sprite where the text should sit. A word longer than `maxWidth` overflows instead of breaking mid-word.
308
333
 
309
- Overlaid `Ti.UI.Label` views remain the answer for text the engine's font cannot draw (accents, system fonts, right-to-left) and for real UI: menus, dialogs, text inputs.
334
+ The built-in font now includes the accented characters listed in
335
+ [api.md](api.md#font); do not assume all accents need an overlay. Ship a custom
336
+ bitmap font for other supported glyph sets. Overlaid `Ti.UI.Label` views remain
337
+ appropriate for system fonts, right-to-left interfaces and native UI such as
338
+ menus, dialogs and text inputs.
310
339
 
311
340
  ### Labels, bars and turrets pinned to a sprite — `attachTo`, `text.js`
312
341
 
@@ -485,7 +514,15 @@ const ball = Game.createSprite({
485
514
 
486
515
  Two things bite here. The sweep **ignores** anything that is not `solidMode: 'block'`, so `swept: true` will not stop a fast enough ball crossing the boundary between frames — keep the speeds inside a container sane. And a `'contain'` solid only holds *circles*: a rect mover runs a resolver that never reads `solidMode` and gets pushed **out** of the boundary instead of held in.
487
516
 
488
- **`linearDamping` is the missing friction, with a caveat.** `drag` only ever worked inside `carMode`; this is the same idea for ordinary sprites, and it is what brings a pool table to rest. But nothing checks contact — it bleeds speed on both axes, in the air as much as on the felt, and along a slope as much as across it. Right for a table, wrong for a hill. Below a combined 4 px/s the engine zeroes the velocity outright, so a deliberate 3 px/s drift dies the moment that sprite also carries damping.
517
+ **`linearDamping` attenuates velocity everywhere.** Car `drag` works inside
518
+ `carMode`; linear damping also works for ordinary sprites and can bring a pool
519
+ table to rest. The attenuation is not contact-only friction: it bleeds speed
520
+ on both axes, in the air as much as on felt, and along a slope as much as
521
+ across it. Below a combined 4 px/s the engine settles coasting or blocked motion, but
522
+ allows effective acceleration to build from rest; the decision is completed
523
+ after contact response. A deliberate unpowered 3 px/s drift with damping still
524
+ stops. See [thrust and damping](physics.md#thrust-inertia-and-linear-damping)
525
+ for the current rest policy and the older powered-launch bug.
489
526
 
490
527
  **A bouncy surface is now a property of the surface.** `restitution` is read off both sides and mixed as `max`, so a springy floor rebounds riders that are not themselves bouncy:
491
528
 
@@ -557,7 +594,7 @@ ship.followPath(diamond, {
557
594
 
558
595
  A non-looping run fires `pathcomplete` and clears itself; `followPath(null)` stops in place. Two points is the minimum — one point is ignored with a warning, not treated as "go there".
559
596
 
560
- The path writes `x`/`y` outright, after velocity and gravity have been integrated, so **a sprite on a path ignores its own physics for placement**. It still feeds the frame delta, though, which means a platform on a looping circuit carries whatever is standing on it — a moving platform is now a path, not a tween ping-pong.
597
+ The path writes `x`/`y` outright, after velocity and gravity have been integrated, so **a sprite on a path ignores its own physics for placement**. In a periodic world the engine aligns the cursor before advancing so crossing the seam retains the intended frame delta; this does not make pathfinding periodic. It still feeds the frame delta, though, which means a platform on a looping circuit carries whatever is standing on it — a moving platform is now a path, not a tween ping-pong.
561
598
 
562
599
  Animation chains are native in the same spirit:
563
600
 
@@ -597,7 +634,7 @@ Run these from a coarse timer or a tap handler — the demo probes at 150 ms —
597
634
 
598
635
  ### Pathfinding around obstacles — `maze.js`, `pointclick.js`
599
636
 
600
- `gameView.findPath(from, to, options)` is grid A\* over the same sprites `raycast` sees: visible, tagged with a `collisionGroup`, nothing else to set up. It returns `{ x, y }` waypoints — or `null` — and those waypoints go straight into `followPath`, so the walk itself runs natively.
637
+ `gameView.findPath(from, to, options)` is grid A\* over the same sprites `raycast` sees: visible, non-`screenFixed`, tagged with a `collisionGroup`. It also includes fully solid cells from matching TileLayers; one-way cells remain walkable. It returns `{ x, y }` waypoints — or `null` — and those waypoints go straight into `followPath`, so the walk itself runs natively.
601
638
 
602
639
  ```javascript
603
640
  gameView.addEventListener('tap', (e) => {
@@ -673,7 +710,16 @@ What still belongs in `setTimeout`: anything that must keep running while the ga
673
710
 
674
711
  ### Hit-stop on impact
675
712
 
676
- Freezing the scene for a few frames makes an impact read before the explosion starts. `timeScale = 0` stops the engine from advancing while it keeps drawing and accepting touches — no render loop to restart, no state to save. (Pattern from Titanium Lander: 140 ms on a crash, 90 ms when a shield absorbs a hit.)
713
+ Giving the scene zero dt for a few frames makes an impact read before the
714
+ explosion starts, while it keeps drawing and accepting touches. This avoids
715
+ restarting the render loop. (Pattern from Titanium Lander: 140 ms on a crash,
716
+ 90 ms when a shield absorbs a hit.)
717
+
718
+ This sets scene dt to zero while touch and explicit property writes remain
719
+ active. Current Rope integration, history, constraints and tether response
720
+ freeze as well; newly created geometry can still initialize. The older
721
+ `3bea2f4` rope exception is fixed. See
722
+ [physics.md](physics.md#verlet-ropes-and-tethers).
677
723
 
678
724
  ```javascript
679
725
  let hitStopTimer = null;