@maccesar/titools 5.1.1 → 5.2.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.
- package/README.md +44 -12
- package/agents/ti-pro.md +4 -0
- package/commands/ti-audit.md +4 -0
- package/lib/config.js +2 -0
- package/package.json +1 -1
- package/skills/ti-admob-unified/SKILL.md +44 -0
- package/skills/ti-admob-unified/agents/openai.yaml +4 -0
- package/skills/ti-admob-unified/assets/example/app.js +1003 -0
- package/skills/ti-admob-unified/assets/example/lib/ads.js +618 -0
- package/skills/ti-admob-unified/assets/example/library-usage.js +104 -0
- package/skills/ti-admob-unified/references/api-reference.md +592 -0
- package/skills/ti-admob-unified/references/banners.md +435 -0
- package/skills/ti-admob-unified/references/contributing.md +228 -0
- package/skills/ti-admob-unified/references/dependencies.md +85 -0
- package/skills/ti-admob-unified/references/fullscreen.md +223 -0
- package/skills/ti-admob-unified/references/game-monetization.md +17 -0
- package/skills/ti-admob-unified/references/getting-started.md +275 -0
- package/skills/ti-admob-unified/references/index.md +30 -0
- package/skills/ti-admob-unified/references/integration.md +85 -0
- package/skills/ti-admob-unified/references/library.md +189 -0
- package/skills/ti-admob-unified/references/mediation.md +132 -0
- package/skills/ti-admob-unified/references/migration.md +255 -0
- package/skills/ti-admob-unified/references/module-changelog.md +54 -0
- package/skills/ti-admob-unified/references/module-license.txt +21 -0
- package/skills/ti-admob-unified/references/privacy.md +254 -0
- package/skills/ti-admob-unified/references/roadmap.md +146 -0
- package/skills/ti-admob-unified/references/source-notes.md +33 -0
- package/skills/ti-admob-unified/references/source-snapshot.json +440 -0
- package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
- package/skills/ti-game/SKILL.md +10 -1
- package/skills/ti-game/references/api.md +10 -2
- package/skills/ti-game/references/debugging-performance.md +6 -0
- package/skills/ti-game/references/examples.md +11 -1
- package/skills/ti-game/references/patterns.md +24 -2
- package/skills/ti-game/references/physics.md +502 -0
- package/skills/ti-game/references/project-setup.md +23 -1
- package/skills/ti-game/references/recipes-action.md +46 -14
- package/skills/ti-game/references/recipes-adventure-effects.md +12 -1
- package/skills/ti-game/references/roadmap.md +2 -2
- package/skills/ti-game/references/tilemaps.md +2 -1
- package/skills/ti-game-assets/SKILL.md +53 -0
- package/skills/ti-game-assets/agents/openai.yaml +4 -0
- package/skills/ti-game-assets/references/alpha.md +26 -0
- package/skills/ti-game-assets/references/art-direction.md +37 -0
- package/skills/ti-game-assets/references/integration.md +49 -0
- package/skills/ti-game-assets/references/manifest.example.json +429 -0
- package/skills/ti-game-assets/references/manifest.md +39 -0
- package/skills/ti-game-assets/references/offline-audio.md +33 -0
- package/skills/ti-game-assets/references/prompts.md +33 -0
- package/skills/ti-game-assets/references/repeating-textures.md +17 -0
- package/skills/ti-game-assets/references/responsible-ads.md +31 -0
- package/skills/ti-game-assets/references/sound-assets.md +42 -0
- package/skills/ti-game-assets/references/vector-sources.md +21 -0
- package/skills/ti-game-assets/scripts/alpha_tools.py +160 -0
- package/skills/ti-game-assets/scripts/requirements.txt +1 -0
- package/skills/ti-game-assets/scripts/validate_manifest.py +267 -0
- package/skills/ti-synthengine/SKILL.md +37 -13
- package/skills/ti-synthengine/references/api.md +397 -31
- package/skills/ti-synthengine/references/audio-engine.md +235 -14
- package/skills/ti-synthengine/references/examples.md +396 -17
- package/skills/ti-synthengine/references/guide.md +331 -12
- package/skills/ti-synthengine/references/recipes.md +212 -22
- package/skills/ti-synthengine/references/recommendations.md +53 -16
- package/skills/ti-synthengine/references/sound-design.md +359 -15
- package/skills/ti-synthengine/references/source-notes.md +121 -0
- package/skills/ti-synthengine/references/source-snapshot.json +105 -0
- package/skills/ti-synthengine/references/stats-performance.json +2992 -0
- package/skills/ti-synthengine/references/stats-performance.md +38 -0
- package/skills/ti-synthengine/references/troubleshooting.md +61 -4
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# Physical models and equations
|
|
2
|
+
|
|
3
|
+
Use this reference to choose movement by its intended feel, derive parameters,
|
|
4
|
+
or explain frame-rate-dependent behavior. A camera angle is not a physics
|
|
5
|
+
model: a top-down character, car, spaceship and billiard ball use different
|
|
6
|
+
rules. The [34-demo catalog](examples.md) covers action, platforms, ball games,
|
|
7
|
+
adventures, board interactions and visual demonstrations.
|
|
8
|
+
|
|
9
|
+
Verified on 2026-10-12 against upstream
|
|
10
|
+
[`3bea2f4007d4aa515e432ec2a016dd14c5c0f2ba`](https://github.com/m1ga/ti.game/tree/3bea2f4007d4aa515e432ec2a016dd14c5c0f2ba).
|
|
11
|
+
Upstream HEAD still has that commit. Local forks and installed binaries can
|
|
12
|
+
contain additional changes; do not silently treat them as this snapshot.
|
|
13
|
+
|
|
14
|
+
Each equation is labeled **Engine** (native implementation), **Demo**
|
|
15
|
+
(application rule) or **Analytical** (a mathematical model for tuning).
|
|
16
|
+
An analytical equation does not imply a matching public property exists.
|
|
17
|
+
JS selects controls and reacts to events; frame-by-frame integration stays native.
|
|
18
|
+
|
|
19
|
+
<!-- TOC-START -->
|
|
20
|
+
## Contents
|
|
21
|
+
|
|
22
|
+
- [Units and conventions](#units-and-conventions)
|
|
23
|
+
- [Model selection across the demos](#model-selection-across-the-demos)
|
|
24
|
+
- [Sprite integration and constant acceleration](#sprite-integration-and-constant-acceleration)
|
|
25
|
+
- [Thrust, inertia and linear damping](#thrust-inertia-and-linear-damping)
|
|
26
|
+
- [Projectiles and additive impulses](#projectiles-and-additive-impulses)
|
|
27
|
+
- [Arcade car equations](#arcade-car-equations)
|
|
28
|
+
- [Direct character control](#direct-character-control)
|
|
29
|
+
- [Jumps, flaps and scrolling runners](#jumps-flaps-and-scrolling-runners)
|
|
30
|
+
- [Landing and impact measurements](#landing-and-impact-measurements)
|
|
31
|
+
- [Static reflection and arcade ball sports](#static-reflection-and-arcade-ball-sports)
|
|
32
|
+
- [Equal-mass billiards and circular containment](#equal-mass-billiards-and-circular-containment)
|
|
33
|
+
- [Slopes and contact friction](#slopes-and-contact-friction)
|
|
34
|
+
- [Verlet ropes and tethers](#verlet-ropes-and-tethers)
|
|
35
|
+
- [Swept movement and periodic worlds](#swept-movement-and-periodic-worlds)
|
|
36
|
+
- [Paths, tweens and navigation](#paths-tweens-and-navigation)
|
|
37
|
+
- [Rhythm timing](#rhythm-timing)
|
|
38
|
+
- [Particle integration](#particle-integration)
|
|
39
|
+
- [Time step and frame rate](#time-step-and-frame-rate)
|
|
40
|
+
- [What needs a different model](#what-needs-a-different-model)
|
|
41
|
+
|
|
42
|
+
<!-- TOC-END -->
|
|
43
|
+
|
|
44
|
+
## Units and conventions
|
|
45
|
+
|
|
46
|
+
| Quantity | Units |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| Position, width, height, path distance | Surface pixels, from guarded GameView `resize` |
|
|
49
|
+
| `velocityX/Y`, `maxSpeed` | px/s |
|
|
50
|
+
| `gravity`, `gravityX`, `thrust`, `enginePower` | px/s² |
|
|
51
|
+
| Native `dt` | Seconds |
|
|
52
|
+
| JS `duration`, `after`, `every`, particle lifetime | Milliseconds |
|
|
53
|
+
| `rotation` | Clockwise degrees; 0 points up |
|
|
54
|
+
| `angularVelocity`, `turnRate` | degrees/s |
|
|
55
|
+
| `linearDamping`, car `drag`, car `grip` | 1/s coefficients, not percentage strings |
|
|
56
|
+
| Rope `damping` | Retained displacement fraction per update |
|
|
57
|
+
| `restitution` | Dimensionless, 0..1 |
|
|
58
|
+
|
|
59
|
+
Origin is top-left; y increases downwards. Position is the sprite anchor.
|
|
60
|
+
For `θ = rotation·π/180`, forward `f=(sin θ,−cos θ)` and right
|
|
61
|
+
`r=(cos θ,sin θ)`. `n` below is a unit contact normal.
|
|
62
|
+
|
|
63
|
+
## Model selection across the demos
|
|
64
|
+
|
|
65
|
+
| Model | Examples | What determines movement |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| Inertial ship | Asteroids | Thrust adds velocity along heading; rotation does not redirect existing momentum |
|
|
68
|
+
| Gravity and replacement jump/flap | Platformer, Flappy, Skate, Flip | Vertical acceleration plus a chosen takeoff velocity |
|
|
69
|
+
| Direct character control | Topdown | Velocity follows input immediately, including diagonal normalization |
|
|
70
|
+
| Arcade car | Racing | Forward/lateral velocity, throttle, steering, drag and grip |
|
|
71
|
+
| Ball with static-surface reflection | Volley, Plinko, Circles, Slopes | Gravity, geometry normals and restitution |
|
|
72
|
+
| Equal-mass circle bodies | Pool, Plinko, Drum, Circles | Bilateral normal impulse and separation |
|
|
73
|
+
| Circular containment | Drum | Keep ball centers inside radius `R−r` |
|
|
74
|
+
| Position constraints | Rope | Verlet displacement and link-distance relaxation |
|
|
75
|
+
| Fast body or instant query | Swept, Raycast, Worldwrap | Swept movement versus a discrete ray query |
|
|
76
|
+
| Prescribed route | Path, Maze, Pointclick, Tilemap, Demoscene | Distance traveled along a path; A* chooses waypoints |
|
|
77
|
+
| Prescribed interpolation | Cards, Puzzle, Basic | Tween destination and easing; drag/snap rules |
|
|
78
|
+
| Timing window | Rhythm | Constant velocity and distance-to-pad acceptance |
|
|
79
|
+
| Ballistic effects | Particles | Native emitter rate, gravity and lifetime; no Sprite solid response |
|
|
80
|
+
| Cross-cutting systems | Camera, Blend, Text, Hitbox, Zones, Timescale | Presentation, geometry, events or clock; not separate locomotion models |
|
|
81
|
+
|
|
82
|
+
Names map to `example/<name>.js`, with lowercase spelling. These groups cover
|
|
83
|
+
all 34 launcher entries; Lunar Lander in the action recipes is a curated
|
|
84
|
+
composition, not an extra upstream demo. Use [examples.md](examples.md) for
|
|
85
|
+
the exact filenames and [api.md](api.md) for public properties.
|
|
86
|
+
|
|
87
|
+
## Sprite integration and constant acceleration
|
|
88
|
+
|
|
89
|
+
**Engine**, without contacts or position overrides:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
vNext = v + a·dt
|
|
93
|
+
pNext = p + vNext·dt
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This is semi-implicit Euler: acceleration before position. In Sprite, the
|
|
97
|
+
order is car integration, angular velocity, thrust and its speed cap,
|
|
98
|
+
gravity, linear damping, position, scroll wrap, path, animation, tween and
|
|
99
|
+
idle wobble. Scene collision/attachment passes can change that result again.
|
|
100
|
+
Do not combine several position writers expecting their displacements to add.
|
|
101
|
+
|
|
102
|
+
**Analytical**, constant acceleration with no cap/contact:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
v(t) = v0 + a·t
|
|
106
|
+
p(t) = p0 + v0·t + 0.5·a·t²
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
That position is the continuous solution, not the exact discrete output.
|
|
110
|
+
At constant step, `t=N·dt`, Euler gives
|
|
111
|
+
`pN=p0+v0·t+0.5·a·(t²+t·dt)`. Multiplying by dt is necessary but does
|
|
112
|
+
not eliminate integration error.
|
|
113
|
+
|
|
114
|
+
**Demo Wind**: gravityX values −900..900 px/s², leaves with vertical gravity
|
|
115
|
+
260 and a puck with gravity 0. Constant sideways acceleration is an arcade
|
|
116
|
+
wind field; it is not drag calculated from relative air velocity.
|
|
117
|
+
|
|
118
|
+
Sources: [Android Sprite.update](https://github.com/m1ga/ti.game/blob/3bea2f4/android/src/ti/game/engine/Sprite.java),
|
|
119
|
+
[iOS TGSprite.update](https://github.com/m1ga/ti.game/blob/3bea2f4/ios/Classes/TGSprite.m).
|
|
120
|
+
|
|
121
|
+
## Thrust, inertia and linear damping
|
|
122
|
+
|
|
123
|
+
**Engine**:
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
rotation += angularVelocity·dt
|
|
127
|
+
f = (sin θ,−cos θ) # updated heading
|
|
128
|
+
v += f·thrust·dt
|
|
129
|
+
if |v| > maxSpeed: v *= maxSpeed/|v|
|
|
130
|
+
v += (gravityX,gravity)·dt
|
|
131
|
+
if linearDamping > 0:
|
|
132
|
+
v *= max(0,1−linearDamping·dt)
|
|
133
|
+
if |v| < 4 px/s: v = (0,0)
|
|
134
|
+
p += v·dt
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The thrust cap runs only in its active branch; direct velocity writes are
|
|
138
|
+
not capped. Asteroids uses 220 degrees/s turning and both thrust and speed
|
|
139
|
+
cap `0.9·min(W,H)`, with no ship damping. Turning changes the nose, not the
|
|
140
|
+
velocity already accumulated. Assisted flight can deliberately add damping,
|
|
141
|
+
but it is resistance everywhere, not contact-only friction.
|
|
142
|
+
|
|
143
|
+
**Analytical** damping: `dv/dt=−cv`, hence `v(t)=v0·exp(−ct)` and speed
|
|
144
|
+
half-life `ln(2)/c`. With constant aligned acceleration, `dv/dt=a−cv`
|
|
145
|
+
tends to `a/c`. The engine approximates damping using `1−c·dt`, then
|
|
146
|
+
applies its rest threshold. Box2D documents the same continuous decay but
|
|
147
|
+
uses `1/(1+c·dt)`; do not substitute that for ti.game's implemented formula.
|
|
148
|
+
[Box2D damping reference](https://box2d.org/documentation/md_simulation.html).
|
|
149
|
+
|
|
150
|
+
### Powered launch below the rest threshold
|
|
151
|
+
|
|
152
|
+
At rest, thrust220 and damping 1.2 at a 1/60 s step give
|
|
153
|
+
`220/60·(1−1.2/60)=3.593` px/s. The threshold erases it before position
|
|
154
|
+
integration, every step. At120 FPS the problem also occurs; thrust100 can
|
|
155
|
+
be erased even at 30 FPS. This follows from the same formula on both platforms.
|
|
156
|
+
|
|
157
|
+
For an application that should brake only while coasting, set damping 0
|
|
158
|
+
while thrust is active and restore its chosen coefficient without thrust.
|
|
159
|
+
This changes powered-flight drag. If resistance must remain during thrust,
|
|
160
|
+
the intended correction belongs in the native rest policy, with tests of
|
|
161
|
+
accelerating bodies and resting bodies on both platforms. Do not invent a
|
|
162
|
+
per-frame JS position loop, give an artificial initial velocity, or increase
|
|
163
|
+
thrust solely to escape the cutoff.
|
|
164
|
+
|
|
165
|
+
Acceleration and resistance together are physically valid. The cutoff is
|
|
166
|
+
the problematic numerical condition. A flame animation proves a visual
|
|
167
|
+
state, not nonzero motion; different device behavior still requires FPS,
|
|
168
|
+
surface, input and installed-build evidence.
|
|
169
|
+
|
|
170
|
+
### Coasting brake feel reference
|
|
171
|
+
|
|
172
|
+
**Application profile**, Titanium Asteroids autopilot, recorded 2026-10-12:
|
|
173
|
+
set `linearDamping: 0` while thrust is active and `linearDamping: 1.2` s⁻¹
|
|
174
|
+
when thrust stops. Native braking then multiplies velocity by
|
|
175
|
+
`max(0, 1-1.2*dt)`, with the installed engine's rest cutoff.
|
|
176
|
+
|
|
177
|
+
César liked the speed of this braking as a reference for other games,
|
|
178
|
+
especially arcade racing, although he did not choose it as the preferred
|
|
179
|
+
feel for Asteroids. Keep the coefficient and activation condition together
|
|
180
|
+
when comparing the feel. This is an optional tuning example, not a default
|
|
181
|
+
for all games or an upstream demo preset. In a racer, evaluate longitudinal
|
|
182
|
+
braking and lateral grip separately before adopting it.
|
|
183
|
+
|
|
184
|
+
## Projectiles and additive impulses
|
|
185
|
+
|
|
186
|
+
**Demo Asteroids**, nose launch:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
pBullet = pShip + f·0.55·SHIP_SIZE
|
|
190
|
+
vBullet = vShip + f·BULLET_SPEED
|
|
191
|
+
BULLET_SPEED = 1.2·min(W,H)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Inherited ship velocity is a deliberate application choice. A fixed
|
|
195
|
+
world-speed projectile instead assigns only `f·speed`.
|
|
196
|
+
|
|
197
|
+
**Analytical** impulse: `Δv=J/m`. The demos add a chosen velocity change
|
|
198
|
+
directly; Sprite has no configurable rigid-body mass. `v+=Δv` bends existing
|
|
199
|
+
motion, whereas `v=vNew` replaces it. Pool strikes are additive, Flappy's
|
|
200
|
+
vertical flap is a replacement, and Drum's SHAKE replaces velocities while
|
|
201
|
+
its contact-driven TUMBLE adds increments.
|
|
202
|
+
|
|
203
|
+
## Arcade car equations
|
|
204
|
+
|
|
205
|
+
**Engine carMode**, forward/right axes from the old heading:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
vForward = v·f; vLateral = v·r
|
|
209
|
+
steerFactor = clamp(vForward/(0.25·maxSpeed),−1,1)
|
|
210
|
+
rotation += steering·turnRate·steerFactor·dt
|
|
211
|
+
vForward += throttle·enginePower·dt
|
|
212
|
+
vForward *= max(0,1−drag·dt)
|
|
213
|
+
vLateral *= max(0,1−grip·dt)
|
|
214
|
+
vForward = clamp(vForward,−0.4·maxSpeed,maxSpeed)
|
|
215
|
+
v = fNew·vForward + rNew·vLateral
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Recomposition uses the updated heading. A stopped car with zero forward
|
|
219
|
+
speed does not steer; reverse forward speed reverses steering direction.
|
|
220
|
+
Lower grip retains more lateral drift; drag reduces forward speed.
|
|
221
|
+
Negative throttle can brake and then reverse. It is an arcade model,
|
|
222
|
+
without wheelbase geometry, individual tires or weight transfer.
|
|
223
|
+
|
|
224
|
+
**Demo Racing** uses `maxSpeed=0.8·min(W,TH)`, where TH is track height,
|
|
225
|
+
`enginePower=1.1·maxSpeed`, turnRate 210, grip 3.2 and drag 0.8. These numeric
|
|
226
|
+
values have the units in the table above, not an interchangeable percentage.
|
|
227
|
+
No JS loop should rotate the velocity to reproduce this native model.
|
|
228
|
+
|
|
229
|
+
Sources: Android Sprite.updateCar and iOS TGSprite.updateCar, at the same
|
|
230
|
+
links as [Sprite integration](#sprite-integration-and-constant-acceleration).
|
|
231
|
+
|
|
232
|
+
## Direct character control
|
|
233
|
+
|
|
234
|
+
**Demo Topdown** uses speed `S=3.5·TILE`. For cardinal input it assigns
|
|
235
|
+
`v=(moveX,moveY)·S`; for diagonal input each axis uses `S/sqrt(2)` so
|
|
236
|
+
the combined magnitude remains S. Releasing input writes zero velocity:
|
|
237
|
+
there is no ship-like inertia. For a general nonzero input vector d, the
|
|
238
|
+
application formula is `v=S·d/|d|`; distinguish this from analog magnitude
|
|
239
|
+
control, which may intentionally retain the stick's partial travel.
|
|
240
|
+
|
|
241
|
+
The demo dog normalizes its target direction at `1.1S` on a decision timer.
|
|
242
|
+
The timer chooses direction; native velocity integrates movement between
|
|
243
|
+
decisions. [Top-down recipe](recipes-action.md#top-down--zelda).
|
|
244
|
+
|
|
245
|
+
## Jumps, flaps and scrolling runners
|
|
246
|
+
|
|
247
|
+
**Demo** Platformer assigns `vx=direction·RUN_SPEED` and, on permitted
|
|
248
|
+
grounded input, `vy=−JUMP`. Flappy assigns `vy=−0.6H` per tap; it does not
|
|
249
|
+
accumulate upward impulses. Its tilt tween is visual, not aerodynamic lift.
|
|
250
|
+
Skate keeps player x fixed and moves terrain/obstacles at `vx=−0.5W`.
|
|
251
|
+
|
|
252
|
+
**Analytical**, gravity `g>0`, takeoff speed magnitude `J`:
|
|
253
|
+
|
|
254
|
+
```text
|
|
255
|
+
timeToApex = J/g
|
|
256
|
+
height = J²/(2g)
|
|
257
|
+
sameLevelFlightTime = 2J/g
|
|
258
|
+
J for desired height h = sqrt(2gh)
|
|
259
|
+
g for height h and apex time T = 2h/T²; J = 2h/T
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| Demo | g | J | Continuous prediction |
|
|
263
|
+
| --- | --- | --- | --- |
|
|
264
|
+
| Platformer | 2.2H | 0.95H | Apex 0.432 s; rise 0.205H |
|
|
265
|
+
| Skate | 2.4H | 0.9H | Apex 0.375 s; same-level flight 0.75 s |
|
|
266
|
+
| Flappy | 1.9H | 0.6H | Isolated flap apex0.316 s; rise 0.095H |
|
|
267
|
+
| Volley players | 2.2H | 0.85H | Apex 0.386 s; rise 0.164H |
|
|
268
|
+
|
|
269
|
+
Contact geometry, ceilings and discrete integration change actual results.
|
|
270
|
+
`oneWay` allows top landings through otherwise passable platforms; exact
|
|
271
|
+
conditions differ by resolver. Moving solids carry grounded riders using
|
|
272
|
+
their frame delta. Skate's raised scrolling road disables `carryRiders` so
|
|
273
|
+
the player is not dragged off screen. [Action recipes](recipes-action.md).
|
|
274
|
+
|
|
275
|
+
## Landing and impact measurements
|
|
276
|
+
|
|
277
|
+
**Engine** `land` reports a grounding transition after solid response.
|
|
278
|
+
Its payload does not include the original velocity. Live `velocityY` can
|
|
279
|
+
already be zero or reflected, and JS event delivery can be later still.
|
|
280
|
+
Do not classify a hard touchdown as gentle by reading that zero value.
|
|
281
|
+
|
|
282
|
+
For sprite solids, `solidimpact.speed` snapshots relative closing normal
|
|
283
|
+
speed before response, compensated for the frame's own acceleration.
|
|
284
|
+
Set `impactThreshold:0` to include gentle positive impacts and check the
|
|
285
|
+
contact normal/group. It is not full vector speed or an exact post-gravity
|
|
286
|
+
pre-contact sample. A rule involving tangential velocity needs separate
|
|
287
|
+
evidence; solid TileLayer cells do not emit `solidimpact`.
|
|
288
|
+
|
|
289
|
+
The [Lunar Lander recipe](recipes-action.md#lunar-lander) uses a static sprite
|
|
290
|
+
pad and this normal-impact contract. A resting body with no positive impact
|
|
291
|
+
does not receive an invented impact merely because its threshold is0.
|
|
292
|
+
|
|
293
|
+
Sources: Android `Scene.resolveSolids`, `dispatchSolidImpact`, `finishContacts`;
|
|
294
|
+
iOS `TGScene.resolveSolids`, `dispatchSolidImpactFor`, `finishContactsFor`;
|
|
295
|
+
see [event payload](api.md#sprite-events).
|
|
296
|
+
|
|
297
|
+
## Static reflection and arcade ball sports
|
|
298
|
+
|
|
299
|
+
**Engine**, circle against a static solid, outward normal:
|
|
300
|
+
|
|
301
|
+
```text
|
|
302
|
+
e = max(eMover,eSolid)
|
|
303
|
+
vn = v·n
|
|
304
|
+
if vn < 0:
|
|
305
|
+
if e > 0 and −e·vn > 40 px/s: v -= (1+e)·vn·n
|
|
306
|
+
else: v -= vn·n
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
This preserves tangential velocity. The small-rebound cutoff belongs to
|
|
310
|
+
this response, not the equal-mass bilateral impulse below. No contact
|
|
311
|
+
friction or collision-generated spin is implied.
|
|
312
|
+
|
|
313
|
+
**Demo Volley** replaces the ball's velocity on player contact:
|
|
314
|
+
`u=normalize(pBall−pPlayer)`, `S=0.85H`,
|
|
315
|
+
`vx=u.x·S`, `vy=min(u.y·S,−0.45S)`. It guarantees an upward component
|
|
316
|
+
instead of simulating two masses. Scenario walls use native restitution;
|
|
317
|
+
player hits use an arcade rule. Plinko uses gravity 1100, restitution 0.42,
|
|
318
|
+
circle pegs and `push` chips.
|
|
319
|
+
|
|
320
|
+
Sources: [Android Scene.resolveCircleSolids](https://github.com/m1ga/ti.game/blob/3bea2f4/android/src/ti/game/engine/Scene.java),
|
|
321
|
+
[iOS TGScene.resolveCircleSolids](https://github.com/m1ga/ti.game/blob/3bea2f4/ios/Classes/TGScene.m).
|
|
322
|
+
|
|
323
|
+
## Equal-mass billiards and circular containment
|
|
324
|
+
|
|
325
|
+
**Engine**, two visible circles, both `push`, each listing the other's group:
|
|
326
|
+
|
|
327
|
+
```text
|
|
328
|
+
n = normalize(centerA−centerB)
|
|
329
|
+
penetration = radiusA+radiusB−distance
|
|
330
|
+
if penetration > slop: pA+=n·penetration/2; pB−=n·penetration/2
|
|
331
|
+
vn = (vA−vB)·n
|
|
332
|
+
e = max(eA,eB)
|
|
333
|
+
if vn < 0:
|
|
334
|
+
j = −(1+e)·vn/2
|
|
335
|
+
vA += j·n; vB -= j·n
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Each pair resolves once. Restitution1 exchanges normal components; tangent
|
|
339
|
+
components remain unchanged. Even differently sized circles are equal-mass
|
|
340
|
+
bodies. Without mutual groups the response falls back to a one-sided solid.
|
|
341
|
+
|
|
342
|
+
Pool uses restitution 0.96, damping 0.62, and a cue velocity increment
|
|
343
|
+
`2600·min(1,tapDistance/(0.55W))` along the tap direction. That limits the
|
|
344
|
+
strike increment, not total speed when a moving cue is hit again. Pockets
|
|
345
|
+
are triggers, not 3D drops.
|
|
346
|
+
|
|
347
|
+
**Analytical**, unequal masses would require
|
|
348
|
+
`j=−(1+e)·vn/(1/mA+1/mB)`, then `vA+=j·n/mA`, `vB−=j·n/mB`.
|
|
349
|
+
That is not the current `push` implementation.
|
|
350
|
+
|
|
351
|
+
**Engine contain**: a ball of radius r inside a container of radius R has
|
|
352
|
+
allowed center distance `R−r`. If exceeded, the correcting normal points
|
|
353
|
+
toward the container center and penetration is `distance−(R−r)`.
|
|
354
|
+
The ball must fit. Drum uses gravity 1400, restitution 0.55, damping 0.35 and
|
|
355
|
+
mutual `push` balls. It mixes impulses/contact response, not a spinning
|
|
356
|
+
rigid drum or fluid simulation. [Shaped-solid patterns](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs).
|
|
357
|
+
|
|
358
|
+
## Slopes and contact friction
|
|
359
|
+
|
|
360
|
+
Rotated ramps require `rotatedRect` geometry for the real face normal.
|
|
361
|
+
The normal component is cancelled/reflected while tangent velocity remains.
|
|
362
|
+
**Analytical**, on a fixed frictionless slope at angle α from horizontal,
|
|
363
|
+
downslope acceleration magnitude is `g·abs(sin α)`.
|
|
364
|
+
|
|
365
|
+
Slopes uses gravity 1100, crate restitution 0, ball0.1 and floor/walls0.5.
|
|
366
|
+
Its ball slides; a circle shape does not add rolling torque. Sprite damping
|
|
367
|
+
acts off the slope too and cannot substitute contact-only tangent friction.
|
|
368
|
+
Smooth uphill walking, connected terrain, mass and torque remain outside
|
|
369
|
+
this model. [Limitations](roadmap.md).
|
|
370
|
+
|
|
371
|
+
## Verlet ropes and tethers
|
|
372
|
+
|
|
373
|
+
**Engine**, a free node:
|
|
374
|
+
|
|
375
|
+
```text
|
|
376
|
+
displacement = (pCurrent−pPrevious)·damping
|
|
377
|
+
pPrevious = pCurrent
|
|
378
|
+
pCurrent += displacement + (0,gravity·dt²)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
For adjacent nodes, `Δ=pB−pA`, `d=|Δ|`, target length L, correction
|
|
382
|
+
`Δ·(d−L)/d` is split between free nodes, or applied fully to the free node
|
|
383
|
+
when the other is pinned. Division has an epsilon guard. `iterations` repeats
|
|
384
|
+
relaxation: more passes stiffen the result at a CPU cost.
|
|
385
|
+
|
|
386
|
+
The first Rope demo uses 14 links of length 0.04H, gravity 1.6H,
|
|
387
|
+
damping 0.985 and 3 constraint passes. Nodes do not automatically receive
|
|
388
|
+
Sprite solid collisions. `tail` plus positive `maxLength` additionally
|
|
389
|
+
clamps head-to-tail distance and removes outward radial velocity. With two
|
|
390
|
+
free sprite ends the correction is split; dragging an end makes the other
|
|
391
|
+
yield. It is a positional tether, not a mass/stiffness Hooke spring.
|
|
392
|
+
|
|
393
|
+
Rope damping is **per update**, not1/s. Before forces and constraints, the
|
|
394
|
+
retained displacement factor over one second at 0.985 would be about 0.635
|
|
395
|
+
at 30 updates,0.404 at 60 and 0.163 at 120. These are not predictions of the
|
|
396
|
+
whole rope's amplitude. An analytical time-normalized alternative would be
|
|
397
|
+
`exp(−c·dt)` or `dRef^(dt/dtRef)`; the engine uses the fixed supplied factor.
|
|
398
|
+
|
|
399
|
+
At the pinned source, Rope.update has no zero-delta early return. Existing
|
|
400
|
+
Verlet displacement and constraints still run at dt 0; `timeScale=0` is not
|
|
401
|
+
a guarantee that rope nodes freeze. This differs from merely setting the
|
|
402
|
+
gravity term to zero. Scene/hardware acceptance is still needed for a
|
|
403
|
+
pause design; stopping the render loop with `pause()` is a different action
|
|
404
|
+
because it also stops rendering. Do not silently change the global pause
|
|
405
|
+
UX to avoid this boundary.
|
|
406
|
+
|
|
407
|
+
Sources: [Android Rope.update](https://github.com/m1ga/ti.game/blob/3bea2f4/android/src/ti/game/engine/Rope.java),
|
|
408
|
+
[iOS TGRope.update](https://github.com/m1ga/ti.game/blob/3bea2f4/ios/Classes/TGRope.m).
|
|
409
|
+
|
|
410
|
+
## Swept movement and periodic worlds
|
|
411
|
+
|
|
412
|
+
**Analytical** per-step movement is `Δp=v·dt`:9600 px/s travels160 px
|
|
413
|
+
at 60 FPS. A final-position overlap can skip a thin wall.
|
|
414
|
+
Swept tests examine the traveled segment; circle pairs have circular sweeps,
|
|
415
|
+
other shape pairs use their respective geometry/approximations. The solid
|
|
416
|
+
sweep skips `contain`/`push`; those still use discrete responses. It is not
|
|
417
|
+
a general multiple-substep rigid-body simulation.
|
|
418
|
+
|
|
419
|
+
Raycast is an instantaneous query, not a gravity-driven projectile.
|
|
420
|
+
`wrapAround` uses screen edges; `wrapX/wrapShift` loops scrolling copies;
|
|
421
|
+
`worldWrapX` defines a periodic horizontal world with participating sprites.
|
|
422
|
+
**Analytical** normalization for `L=maxX−minX` is
|
|
423
|
+
`x=minX+((x−minX)%L+L)%L`; nearest image displacement is
|
|
424
|
+
`dx−L·round(dx/L)`. Raycast, A*, particles, ropes and skid trails do not
|
|
425
|
+
repeat at the seam. [Worldwrap contract](api.md#physics).
|
|
426
|
+
|
|
427
|
+
## Paths, tweens and navigation
|
|
428
|
+
|
|
429
|
+
**Engine Path** accumulates `distance+=speed·dt`. Within a segment,
|
|
430
|
+
`u=(distance−segmentStart)/segmentLength`,
|
|
431
|
+
`p=pStart+u·(pEnd−pStart)`. Optional rotation is
|
|
432
|
+
`atan2(dx,−dy)·180/π`. A* chooses the waypoints; it does not apply a force.
|
|
433
|
+
Maze/Tilemap use4·TILE px/s. Path motion overrides integrated position;
|
|
434
|
+
a later position tween can override the path again.
|
|
435
|
+
|
|
436
|
+
**Engine Tween** uses `u=clamp(elapsed/duration,0,1)` after its delay,
|
|
437
|
+
then `value=from+(to−from)·Easing(u)`. Cards, puzzles and Basic use
|
|
438
|
+
prescribed destinations/gestures, not conservation of momentum.
|
|
439
|
+
Camera/parallax, blend, text, flip and pixel snapping are presentation;
|
|
440
|
+
they do not change those motion equations. Snapping rounds the drawn anchor,
|
|
441
|
+
not physical position, and cannot eliminate every rotational raster change.
|
|
442
|
+
|
|
443
|
+
Sources: [Path.advance](https://github.com/m1ga/ti.game/blob/3bea2f4/android/src/ti/game/engine/Path.java),
|
|
444
|
+
[Tween.update](https://github.com/m1ga/ti.game/blob/3bea2f4/android/src/ti/game/engine/Tween.java).
|
|
445
|
+
|
|
446
|
+
## Rhythm timing
|
|
447
|
+
|
|
448
|
+
**Demo**, `vy=(PAD_Y+NOTE)/1.6`, starting at `y=−NOTE`, gives a 1.6 s
|
|
449
|
+
trip to the pad. GOOD allows 0.08H distance and PERFECT0.03H.
|
|
450
|
+
**Analytical** temporal error is `abs(y−PAD_Y)/abs(vy)`; changing fall speed
|
|
451
|
+
changes the milliseconds represented by a fixed pixel window.
|
|
452
|
+
|
|
453
|
+
At the demo's stated 150 BPM, quarter duration is `60000/150=400 ms`;
|
|
454
|
+
an eighth is200 ms. The400 ms spawn interval and 1.6s travel are one and four
|
|
455
|
+
quarters, respectively. The upstream comments call them eighths, and the
|
|
456
|
+
old action recipe repeated that error. The corrected recipe preserves timing.
|
|
457
|
+
|
|
458
|
+
This is a comment/recipe error, not evidence of a faulty native beat engine.
|
|
459
|
+
The demo uses `setInterval(spawn,SPAWN_MS)` and independent `music.play()`;
|
|
460
|
+
it has no audio-position-driven scheduling. Exact musical synchronization,
|
|
461
|
+
actual asset tempo and phase would need separate measurement. A comment-only
|
|
462
|
+
upstream patch should not halve SPAWN_MS or replace the motor.
|
|
463
|
+
|
|
464
|
+
Source: [Rhythm demo](https://github.com/m1ga/ti.game/blob/3bea2f4/example/rhythm.js).
|
|
465
|
+
|
|
466
|
+
## Particle integration
|
|
467
|
+
|
|
468
|
+
**Engine** accumulates `rate·dt`, emits the integer part and keeps the
|
|
469
|
+
remainder. Live particles age, receive `vy+=gravity·dt`, then position
|
|
470
|
+
updates with the new velocity. At lifetime they retire. They are effects,
|
|
471
|
+
not Sprite solids or automatic damage sensors. [Particle recipes](recipes-adventure-effects.md#particles).
|
|
472
|
+
|
|
473
|
+
## Time step and frame rate
|
|
474
|
+
|
|
475
|
+
Both renderers derive a variable real dt, cap it at 0.1s and then scale by
|
|
476
|
+
`max(0,timeScale)`. They do not run a fixed-step accumulator with physics
|
|
477
|
+
substeps. `maxFps` is a cap, not a guarantee of exact dt. Differences can
|
|
478
|
+
come from Euler error, rest/rebound thresholds, per-update Rope damping,
|
|
479
|
+
surface-based parameters, input/lifecycle or different installed binaries.
|
|
480
|
+
|
|
481
|
+
GameView timers follow scene time, conventional JS timers do not. Rendering
|
|
482
|
+
and touch continue under timeScale 0; the Rope exception above prevents an
|
|
483
|
+
unqualified promise that every position stays unchanged.
|
|
484
|
+
|
|
485
|
+
For tuning, capture the build, actual FPS, W/H, timeScale, forces and
|
|
486
|
+
velocities. Check rest-to-powered motion, releases, turns, contacts and pauses
|
|
487
|
+
at 30/60/120 FPS, then validate on the target hardware. Scalar equations and
|
|
488
|
+
source parity are useful evidence, not certificates of identical device feel.
|
|
489
|
+
[Telemetry workflow](debugging-performance.md).
|
|
490
|
+
|
|
491
|
+
Sources: [Android SceneRenderer](https://github.com/m1ga/ti.game/blob/3bea2f4/android/src/ti/game/engine/SceneRenderer.java),
|
|
492
|
+
[iOS TGSceneRenderer](https://github.com/m1ga/ti.game/blob/3bea2f4/ios/Classes/TGSceneRenderer.m).
|
|
493
|
+
|
|
494
|
+
## What needs a different model
|
|
495
|
+
|
|
496
|
+
Assisted spacecraft flight can use deliberate resistance, but it is not
|
|
497
|
+
airplane aerodynamics. A fuller aircraft model needs relative airspeed,
|
|
498
|
+
lift, drag, stall, altitude and bank response. Ships, tires, springs,
|
|
499
|
+
variable masses and collision torque also need separate modeling choices.
|
|
500
|
+
Do not claim those simulations from a sprite's artwork or camera angle.
|
|
501
|
+
See [roadmap.md](roadmap.md) before naming a missing API, and retain the
|
|
502
|
+
distinction between analytical tuning, application rules and native support.
|
|
@@ -78,7 +78,7 @@ ti build -p ios --build-only # macOS only → ios/dist/ti.game-iphone-<ve
|
|
|
78
78
|
|
|
79
79
|
## Classic projects
|
|
80
80
|
|
|
81
|
-
`require('ti.game')` anywhere and add the GameView to a window. The
|
|
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):
|
|
82
82
|
|
|
83
83
|
```javascript
|
|
84
84
|
// Resources/flappy.js
|
|
@@ -332,6 +332,28 @@ function unloadLevel(level) {
|
|
|
332
332
|
|
|
333
333
|
A single-level game with a handful of sheets should not call it at all: the textures die with the window.
|
|
334
334
|
|
|
335
|
+
### Retained iOS scenes and multiple GameViews
|
|
336
|
+
|
|
337
|
+
In the pinned iOS source, `TiGameGameView.didMoveToWindow` calls
|
|
338
|
+
`shutdownRendering` when an attached view loses its window. That teardown
|
|
339
|
+
sets a terminal shutdown flag and releases the renderer. Reattaching the
|
|
340
|
+
same proxy or calling resume is not a documented reconstruction mechanism.
|
|
341
|
+
If a navigation/modal presentation detaches a backdrop that must remain
|
|
342
|
+
alive, keep that GameView attached or create a fresh one on return; rebuilding
|
|
343
|
+
only its Sprite session is insufficient. Verify the actual UIKit presentation
|
|
344
|
+
and installed module on-device.
|
|
345
|
+
|
|
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.
|
|
352
|
+
|
|
353
|
+
Sources: upstream `ios/Classes/TiGameGameView.m` (`didMoveToWindow`,
|
|
354
|
+
`shutdownRendering`) and `ios/Classes/TGGamepadController.m`
|
|
355
|
+
(`attachController`, `detachController`) at `3bea2f4`.
|
|
356
|
+
|
|
335
357
|
### LiveView reloads
|
|
336
358
|
|
|
337
359
|
A LiveView reload replaces Titanium's JS runtime while the native side survives, which used to leave the previous `GameView`'s render loop running — every reload added another GL thread, and the audio engine handed the new runtime sample ids the old one still owned.
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
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
|
+
Use [physics.md](physics.md) for the implemented equations, units, jump tuning,
|
|
6
|
+
inertia versus direct control, and numerical limits. Recipes select native
|
|
7
|
+
mechanisms; they do not replace that model reference.
|
|
8
|
+
|
|
5
9
|
<!-- TOC-START -->
|
|
6
10
|
## Contents
|
|
7
11
|
|
|
@@ -19,7 +23,7 @@ Production-oriented patterns distilled from the upstream action demos at `ti.gam
|
|
|
19
23
|
|
|
20
24
|
## Tap-to-flap
|
|
21
25
|
|
|
22
|
-
Gravity plus
|
|
26
|
+
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.
|
|
23
27
|
|
|
24
28
|
```javascript
|
|
25
29
|
const pig = Game.createSprite({
|
|
@@ -236,8 +240,9 @@ let lastFacing = 'down';
|
|
|
236
240
|
let walking = false;
|
|
237
241
|
|
|
238
242
|
function applyMovement() {
|
|
239
|
-
|
|
240
|
-
player.
|
|
243
|
+
const speed = (moveX !== 0 && moveY !== 0) ? SPEED / Math.sqrt(2) : SPEED;
|
|
244
|
+
player.velocityX = moveX * speed;
|
|
245
|
+
player.velocityY = moveY * speed;
|
|
241
246
|
if (moveX === 0 && moveY === 0) {
|
|
242
247
|
player.stop();
|
|
243
248
|
walking = false;
|
|
@@ -418,7 +423,13 @@ Rocks are round (`hitboxShape: 'circle'`), drift on random velocities, spin on `
|
|
|
418
423
|
|
|
419
424
|
## Lunar lander
|
|
420
425
|
|
|
421
|
-
Same flight model as Asteroids, with gravity
|
|
426
|
+
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
|
|
428
|
+
after the solid response: live velocity may already be zero or reflected.
|
|
429
|
+
For a static sprite pad, use the snapshotted normal speed in `solidimpact`.
|
|
430
|
+
It is compensated for this frame's acceleration, not the full incoming
|
|
431
|
+
velocity vector. Tiles do not emit that event; use a sprite pad when the
|
|
432
|
+
impact must be measured. See [landing evidence](physics.md#landing-and-impact-measurements).
|
|
422
433
|
|
|
423
434
|
```javascript
|
|
424
435
|
const lander = Game.createSprite({
|
|
@@ -428,6 +439,7 @@ const lander = Game.createSprite({
|
|
|
428
439
|
gravity: world.gravity, // per-world, e.g. H * 0.35 for the Moon
|
|
429
440
|
maxSpeed: MAX_SPEED,
|
|
430
441
|
hitboxScale: 0.8,
|
|
442
|
+
impactThreshold: 0, // include gentle physical impacts
|
|
431
443
|
solidWith: ['terrain', 'pad'],
|
|
432
444
|
collidesWith: ['hazard']
|
|
433
445
|
});
|
|
@@ -435,12 +447,12 @@ const lander = Game.createSprite({
|
|
|
435
447
|
const MAX_LANDING_SPEED = H * 0.18;
|
|
436
448
|
const MAX_LANDING_TILT = 12; // degrees
|
|
437
449
|
|
|
438
|
-
lander.addEventListener('
|
|
439
|
-
const speed =
|
|
450
|
+
lander.addEventListener('solidimpact', (e) => {
|
|
451
|
+
const speed = e.speed; // immutable contact payload, not live velocity
|
|
440
452
|
const tilt = Math.abs(((lander.rotation % 360) + 540) % 360 - 180);
|
|
441
453
|
const onPad = e.group === 'pad';
|
|
442
454
|
|
|
443
|
-
if (onPad && speed <= MAX_LANDING_SPEED && tilt <= MAX_LANDING_TILT) {
|
|
455
|
+
if (onPad && e.normalY < -0.7 && speed <= MAX_LANDING_SPEED && tilt <= MAX_LANDING_TILT) {
|
|
444
456
|
succeed();
|
|
445
457
|
return;
|
|
446
458
|
}
|
|
@@ -458,12 +470,17 @@ function crash() {
|
|
|
458
470
|
}
|
|
459
471
|
```
|
|
460
472
|
|
|
461
|
-
|
|
473
|
+
Fuel and control decisions use a coarse game-clock tick. `BURN_RATE` is fuel
|
|
474
|
+
units per game second; each nominal tick removes the rate times its interval.
|
|
475
|
+
GameView timers can fire late and do not catch up after stalls, so this is a
|
|
476
|
+
cadence approximation, not exact elapsed-time accounting. Store the timer ID
|
|
477
|
+
and cancel it with `gameView.cancelTimer(timer)` on cleanup:
|
|
462
478
|
|
|
463
479
|
```javascript
|
|
464
|
-
|
|
480
|
+
const CONTROL_MS = 80;
|
|
481
|
+
timer = gameView.every(CONTROL_MS, () => {
|
|
465
482
|
if (mainThrusterHeld && fuel > 0) {
|
|
466
|
-
fuel
|
|
483
|
+
fuel = Math.max(0, fuel - BURN_RATE * CONTROL_MS / 1000);
|
|
467
484
|
lander.thrust = THRUST;
|
|
468
485
|
exhaust.emitting = true;
|
|
469
486
|
} else {
|
|
@@ -471,10 +488,13 @@ timer = setInterval(() => {
|
|
|
471
488
|
exhaust.emitting = false;
|
|
472
489
|
}
|
|
473
490
|
fuelLabel.text = Math.max(0, Math.round(fuel)) + '%';
|
|
474
|
-
}
|
|
491
|
+
});
|
|
475
492
|
```
|
|
476
493
|
|
|
477
|
-
|
|
494
|
+
Set `lander.gravityX = world.wind` in px/s² when the wind changes. Native
|
|
495
|
+
integration applies it every frame; do not add velocity repeatedly from a JS
|
|
496
|
+
timer. For lateral-velocity limits or exact vector touchdown tests, normal
|
|
497
|
+
impact speed alone is insufficient: design a separate measurement contract.
|
|
478
498
|
|
|
479
499
|
## Endless runner
|
|
480
500
|
|
|
@@ -599,7 +619,15 @@ function hitLane(lane, pad) {
|
|
|
599
619
|
}
|
|
600
620
|
```
|
|
601
621
|
|
|
602
|
-
An invisible `misszone` trigger below the pads catches gems that got away and
|
|
622
|
+
An invisible `misszone` trigger below the pads catches gems that got away and
|
|
623
|
+
counts the miss. `FALL_SPEED = (PAD_Y + NOTE) / 1.6` gives a 1.6 s trip from
|
|
624
|
+
`-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.
|
|
628
|
+
Position windows convert to time with `windowSeconds = windowPx / FALL_SPEED`.
|
|
629
|
+
The demo uses a JS interval and independent music playback, not an audio-clock
|
|
630
|
+
beat scheduler; see [rhythm timing](physics.md#rhythm-timing).
|
|
603
631
|
|
|
604
632
|
## Ball sports / breakout physics
|
|
605
633
|
|
|
@@ -634,7 +662,11 @@ ball.addEventListener('collision', (e) => {
|
|
|
634
662
|
|
|
635
663
|
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.
|
|
636
664
|
|
|
637
|
-
A `restitution: 1` ball
|
|
665
|
+
A `restitution: 1` ball reflects larger impacts without restitution loss;
|
|
666
|
+
the small-bounce cutoff can still settle it. Restitution can live on the
|
|
667
|
+
floor, so one springy platform can bounce otherwise non-bouncy riders.
|
|
668
|
+
Round obstacles, mutual push bodies and pool damping are in
|
|
669
|
+
[Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs).
|
|
638
670
|
|
|
639
671
|
**Opponent AI** on an 80 ms timer, reading a handful of live properties per tick:
|
|
640
672
|
|