@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.
- package/CONTRIBUTING.md +69 -0
- package/README.md +112 -72
- package/agents/ti-pro.md +5 -1
- package/commands/ti-audit.md +5 -1
- package/docs/examples.md +91 -0
- package/docs/knowledge-index.md +57 -0
- package/lib/config.js +2 -0
- package/lib/downloader.js +1 -1
- package/package.json +4 -2
- package/skills/purgetss/references/multi-density-images.md +1 -1
- package/skills/ti-admob-unified/SKILL.md +58 -0
- package/skills/ti-admob-unified/agents/openai.yaml +4 -0
- package/skills/ti-admob-unified/assets/example/app.js +1207 -0
- package/skills/ti-admob-unified/assets/example/lib/ads.js +646 -0
- package/skills/ti-admob-unified/assets/example/library-usage.js +136 -0
- package/skills/ti-admob-unified/references/api-reference.md +615 -0
- package/skills/ti-admob-unified/references/banners.md +435 -0
- package/skills/ti-admob-unified/references/contributing.md +233 -0
- package/skills/ti-admob-unified/references/dependencies.md +91 -0
- package/skills/ti-admob-unified/references/fullscreen.md +238 -0
- package/skills/ti-admob-unified/references/game-monetization.md +148 -0
- package/skills/ti-admob-unified/references/getting-started.md +319 -0
- package/skills/ti-admob-unified/references/index.md +33 -0
- package/skills/ti-admob-unified/references/integration.md +87 -0
- package/skills/ti-admob-unified/references/library.md +220 -0
- package/skills/ti-admob-unified/references/mediation-parity.md +17 -0
- package/skills/ti-admob-unified/references/mediation.md +145 -0
- package/skills/ti-admob-unified/references/migration.md +259 -0
- package/skills/ti-admob-unified/references/module-changelog.md +84 -0
- package/skills/ti-admob-unified/references/module-license.txt +21 -0
- package/skills/ti-admob-unified/references/privacy.md +281 -0
- package/skills/ti-admob-unified/references/production.md +67 -0
- package/skills/ti-admob-unified/references/rewarded-interstitial.md +101 -0
- package/skills/ti-admob-unified/references/roadmap.md +143 -0
- package/skills/ti-admob-unified/references/source-notes.md +80 -0
- package/skills/ti-admob-unified/references/source-snapshot.json +644 -0
- package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
- package/skills/ti-game/SKILL.md +31 -5
- package/skills/ti-game/references/api.md +36 -24
- package/skills/ti-game/references/debugging-performance.md +22 -7
- package/skills/ti-game/references/examples.md +113 -20
- package/skills/ti-game/references/input.md +101 -0
- package/skills/ti-game/references/patterns.md +56 -10
- package/skills/ti-game/references/physics.md +530 -0
- package/skills/ti-game/references/project-setup.md +131 -47
- package/skills/ti-game/references/recipes-action.md +149 -18
- package/skills/ti-game/references/recipes-adventure-effects.md +47 -4
- package/skills/ti-game/references/roadmap.md +11 -4
- package/skills/ti-game/references/source-notes.md +81 -0
- package/skills/ti-game/references/tilemaps.md +13 -4
- 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 +77 -67
- package/skills/ti-synthengine/assets/example/README.md +84 -0
- package/skills/ti-synthengine/assets/example/app.js +1178 -0
- package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +83 -0
- package/skills/ti-synthengine/assets/example/lib/sequenceScore.js +25 -0
- package/skills/ti-synthengine/references/advanced-examples.md +174 -0
- package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
- package/skills/ti-synthengine/references/android-sim80-correction.json +162 -0
- package/skills/ti-synthengine/references/api.md +573 -153
- package/skills/ti-synthengine/references/audio-engine.md +272 -124
- package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
- package/skills/ti-synthengine/references/examples.md +355 -106
- package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-physical-validation.json +1918 -0
- package/skills/ti-synthengine/references/guide.md +426 -75
- package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
- package/skills/ti-synthengine/references/performance-review.json +1146 -0
- package/skills/ti-synthengine/references/physical-validation.json +1457 -0
- package/skills/ti-synthengine/references/recipes-interaction.md +549 -0
- package/skills/ti-synthengine/references/recipes.md +434 -420
- package/skills/ti-synthengine/references/recommendations.md +72 -85
- package/skills/ti-synthengine/references/sound-design.md +375 -155
- package/skills/ti-synthengine/references/source-notes.md +109 -0
- package/skills/ti-synthengine/references/source-snapshot.json +452 -0
- package/skills/ti-synthengine/references/stats-performance.json +2992 -0
- package/skills/ti-synthengine/references/stats-performance.md +82 -0
- package/skills/ti-synthengine/references/studio-validation.json +6202 -0
- package/skills/ti-synthengine/references/troubleshooting.md +123 -78
- package/skills/ti-synthengine/references/unified-bypass-performance.json +22425 -0
- package/skills/ti-synthengine/references/unified-validation.json +314 -0
- package/AGENTS-VERCEL-RESEARCH.md +0 -175
|
@@ -1,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.
|
|
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.
|
|
29
|
-
└── iphone/ti.game/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.
|
|
41
|
-
<module platform="iphone" version="0.
|
|
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
|
-
|
|
49
|
+
### Requirements and source builds
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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)
|
|
62
|
+
```
|
|
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
|
|
77
73
|
```
|
|
78
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
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
|
@@ -332,6 +341,29 @@ function unloadLevel(level) {
|
|
|
332
341
|
|
|
333
342
|
A single-level game with a handful of sheets should not call it at all: the textures die with the window.
|
|
334
343
|
|
|
344
|
+
### Retained iOS scenes and multiple GameViews
|
|
345
|
+
|
|
346
|
+
In the pinned iOS source, `TiGameGameView.didMoveToWindow` calls
|
|
347
|
+
`shutdownRendering` when an attached view loses its window. That teardown
|
|
348
|
+
sets a terminal shutdown flag and releases the renderer. Reattaching the
|
|
349
|
+
same proxy or calling resume is not a documented reconstruction mechanism.
|
|
350
|
+
If a navigation/modal presentation detaches a backdrop that must remain
|
|
351
|
+
alive, keep that GameView attached or create a fresh one on return; rebuilding
|
|
352
|
+
only its Sprite session is insufficient. Verify the actual UIKit presentation
|
|
353
|
+
and installed module on-device.
|
|
354
|
+
|
|
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).
|
|
361
|
+
|
|
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.
|
|
366
|
+
|
|
335
367
|
### LiveView reloads
|
|
336
368
|
|
|
337
369
|
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.
|
|
@@ -351,14 +383,66 @@ Since 2026-08-23 the module retires the previous generation itself: stale render
|
|
|
351
383
|
|
|
352
384
|
## Platform differences
|
|
353
385
|
|
|
354
|
-
The
|
|
386
|
+
The public API is mostly shared. Preserve these platform contracts:
|
|
355
387
|
|
|
356
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`.
|
|
357
|
-
- **The iOS Simulator renders at a 1x logical drawable** (
|
|
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.
|
|
358
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`.
|
|
359
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`.
|
|
360
392
|
- **Sound formats**: WAV, MP3 and OGG on Android; WAV, MP3 and M4A on iOS. WAV for effects, MP3 for music covers both.
|
|
361
393
|
- **TileLayer grid configuration**: assign `legend`, `firstGid`, `cols`, and `rows` in `createTileLayer()`. iOS accepts later writes; Android treats them as creation-time inputs.
|
|
362
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.
|
|
363
|
-
- **Performance telemetry**: `averagePresentMs` and `presentFailures` exist only on
|
|
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.
|
|
364
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,10 +1,15 @@
|
|
|
1
1
|
# Action-game recipes
|
|
2
2
|
|
|
3
|
-
Production-oriented patterns distilled from the
|
|
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
|
+
|
|
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.
|
|
4
8
|
|
|
5
9
|
<!-- TOC-START -->
|
|
6
10
|
## Contents
|
|
7
11
|
|
|
12
|
+
- [Shared digital actions](#shared-digital-actions)
|
|
8
13
|
- [Tap-to-flap](#tap-to-flap)
|
|
9
14
|
- [Platformer](#platformer)
|
|
10
15
|
- [Top-down / Zelda](#top-down--zelda)
|
|
@@ -17,9 +22,83 @@ Production-oriented patterns distilled from the upstream action demos at `ti.gam
|
|
|
17
22
|
|
|
18
23
|
<!-- TOC-END -->
|
|
19
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
|
+
|
|
20
99
|
## Tap-to-flap
|
|
21
100
|
|
|
22
|
-
Gravity plus
|
|
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.
|
|
23
102
|
|
|
24
103
|
```javascript
|
|
25
104
|
const pig = Game.createSprite({
|
|
@@ -64,7 +143,8 @@ pig.addEventListener('collision', (e) => {
|
|
|
64
143
|
});
|
|
65
144
|
```
|
|
66
145
|
|
|
67
|
-
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.
|
|
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.
|
|
68
148
|
|
|
69
149
|
## Platformer
|
|
70
150
|
|
|
@@ -172,7 +252,10 @@ Set `carryRiders: false` on terrain that scrolls under a player who should stay
|
|
|
172
252
|
|
|
173
253
|
## Top-down / Zelda
|
|
174
254
|
|
|
175
|
-
A tile map from a string array, plus `ySort` for depth.
|
|
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.
|
|
176
259
|
|
|
177
260
|
```javascript
|
|
178
261
|
const MAP = [
|
|
@@ -200,7 +283,7 @@ for (let row = 0; row < ROWS; row++) {
|
|
|
200
283
|
width: TILE,
|
|
201
284
|
height: TILE,
|
|
202
285
|
zIndex: 0,
|
|
203
|
-
collisionGroup: (kind === 'W') ? 'solid' : null // only water
|
|
286
|
+
collisionGroup: (kind === 'W') ? 'solid' : null // only water tiles block; house is a separate solid
|
|
204
287
|
}));
|
|
205
288
|
}
|
|
206
289
|
}
|
|
@@ -236,8 +319,9 @@ let lastFacing = 'down';
|
|
|
236
319
|
let walking = false;
|
|
237
320
|
|
|
238
321
|
function applyMovement() {
|
|
239
|
-
|
|
240
|
-
player.
|
|
322
|
+
const speed = (moveX !== 0 && moveY !== 0) ? SPEED / Math.sqrt(2) : SPEED;
|
|
323
|
+
player.velocityX = moveX * speed;
|
|
324
|
+
player.velocityY = moveY * speed;
|
|
241
325
|
if (moveX === 0 && moveY === 0) {
|
|
242
326
|
player.stop();
|
|
243
327
|
walking = false;
|
|
@@ -278,6 +362,18 @@ timer = setInterval(() => {
|
|
|
278
362
|
}, 100);
|
|
279
363
|
```
|
|
280
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
|
+
|
|
281
377
|
## Top-down racer
|
|
282
378
|
|
|
283
379
|
`carMode` gives you throttle, steering and emergent drift. You supply the track and the lap logic.
|
|
@@ -415,10 +511,21 @@ warnTimer = setInterval(() => {
|
|
|
415
511
|
```
|
|
416
512
|
|
|
417
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.
|
|
418
519
|
|
|
419
520
|
## Lunar lander
|
|
420
521
|
|
|
421
|
-
Same flight model as Asteroids, with gravity
|
|
522
|
+
Same flight model as Asteroids, with gravity and a touchdown rule. This is a
|
|
523
|
+
curated recipe, not one of the 35 module demos. `land` reports grounding
|
|
524
|
+
after the solid response: live velocity may already be zero or reflected.
|
|
525
|
+
For a static sprite pad, use the snapshotted normal speed in `solidimpact`.
|
|
526
|
+
It is compensated for this frame's acceleration, not the full incoming
|
|
527
|
+
velocity vector. Tiles do not emit that event; use a sprite pad when the
|
|
528
|
+
impact must be measured. See [landing evidence](physics.md#landing-and-impact-measurements).
|
|
422
529
|
|
|
423
530
|
```javascript
|
|
424
531
|
const lander = Game.createSprite({
|
|
@@ -428,6 +535,7 @@ const lander = Game.createSprite({
|
|
|
428
535
|
gravity: world.gravity, // per-world, e.g. H * 0.35 for the Moon
|
|
429
536
|
maxSpeed: MAX_SPEED,
|
|
430
537
|
hitboxScale: 0.8,
|
|
538
|
+
impactThreshold: 0, // include gentle physical impacts
|
|
431
539
|
solidWith: ['terrain', 'pad'],
|
|
432
540
|
collidesWith: ['hazard']
|
|
433
541
|
});
|
|
@@ -435,12 +543,12 @@ const lander = Game.createSprite({
|
|
|
435
543
|
const MAX_LANDING_SPEED = H * 0.18;
|
|
436
544
|
const MAX_LANDING_TILT = 12; // degrees
|
|
437
545
|
|
|
438
|
-
lander.addEventListener('
|
|
439
|
-
const speed =
|
|
546
|
+
lander.addEventListener('solidimpact', (e) => {
|
|
547
|
+
const speed = e.speed; // immutable contact payload, not live velocity
|
|
440
548
|
const tilt = Math.abs(((lander.rotation % 360) + 540) % 360 - 180);
|
|
441
549
|
const onPad = e.group === 'pad';
|
|
442
550
|
|
|
443
|
-
if (onPad && speed <= MAX_LANDING_SPEED && tilt <= MAX_LANDING_TILT) {
|
|
551
|
+
if (onPad && e.normalY < -0.7 && speed <= MAX_LANDING_SPEED && tilt <= MAX_LANDING_TILT) {
|
|
444
552
|
succeed();
|
|
445
553
|
return;
|
|
446
554
|
}
|
|
@@ -458,12 +566,17 @@ function crash() {
|
|
|
458
566
|
}
|
|
459
567
|
```
|
|
460
568
|
|
|
461
|
-
|
|
569
|
+
Fuel and control decisions use a coarse game-clock tick. `BURN_RATE` is fuel
|
|
570
|
+
units per game second; each nominal tick removes the rate times its interval.
|
|
571
|
+
GameView timers can fire late and do not catch up after stalls, so this is a
|
|
572
|
+
cadence approximation, not exact elapsed-time accounting. Store the timer ID
|
|
573
|
+
and cancel it with `gameView.cancelTimer(timer)` on cleanup:
|
|
462
574
|
|
|
463
575
|
```javascript
|
|
464
|
-
|
|
576
|
+
const CONTROL_MS = 80;
|
|
577
|
+
timer = gameView.every(CONTROL_MS, () => {
|
|
465
578
|
if (mainThrusterHeld && fuel > 0) {
|
|
466
|
-
fuel
|
|
579
|
+
fuel = Math.max(0, fuel - BURN_RATE * CONTROL_MS / 1000);
|
|
467
580
|
lander.thrust = THRUST;
|
|
468
581
|
exhaust.emitting = true;
|
|
469
582
|
} else {
|
|
@@ -471,10 +584,13 @@ timer = setInterval(() => {
|
|
|
471
584
|
exhaust.emitting = false;
|
|
472
585
|
}
|
|
473
586
|
fuelLabel.text = Math.max(0, Math.round(fuel)) + '%';
|
|
474
|
-
}
|
|
587
|
+
});
|
|
475
588
|
```
|
|
476
589
|
|
|
477
|
-
|
|
590
|
+
Set `lander.gravityX = world.wind` in px/s² when the wind changes. Native
|
|
591
|
+
integration applies it every frame; do not add velocity repeatedly from a JS
|
|
592
|
+
timer. For lateral-velocity limits or exact vector touchdown tests, normal
|
|
593
|
+
impact speed alone is insufficient: design a separate measurement contract.
|
|
478
594
|
|
|
479
595
|
## Endless runner
|
|
480
596
|
|
|
@@ -599,7 +715,14 @@ function hitLane(lane, pad) {
|
|
|
599
715
|
}
|
|
600
716
|
```
|
|
601
717
|
|
|
602
|
-
An invisible `misszone` trigger below the pads catches gems that got away and
|
|
718
|
+
An invisible `misszone` trigger below the pads catches gems that got away and
|
|
719
|
+
counts the miss. `FALL_SPEED = (PAD_Y + NOTE) / 1.6` gives a 1.6 s trip from
|
|
720
|
+
`-NOTE` to `PAD_Y`. At the stated 150 BPM, `60000 / 150 = 400` ms is a quarter
|
|
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.
|
|
723
|
+
Position windows convert to time with `windowSeconds = windowPx / FALL_SPEED`.
|
|
724
|
+
The demo uses a JS interval and independent music playback, not an audio-clock
|
|
725
|
+
beat scheduler; see [rhythm timing](physics.md#rhythm-timing).
|
|
603
726
|
|
|
604
727
|
## Ball sports / breakout physics
|
|
605
728
|
|
|
@@ -632,9 +755,17 @@ ball.addEventListener('collision', (e) => {
|
|
|
632
755
|
});
|
|
633
756
|
```
|
|
634
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
|
+
|
|
635
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.
|
|
636
763
|
|
|
637
|
-
A `restitution: 1` ball
|
|
764
|
+
A `restitution: 1` ball reflects larger impacts without restitution loss;
|
|
765
|
+
the small-bounce cutoff can still settle it. Restitution can live on the
|
|
766
|
+
floor, so one springy platform can bounce otherwise non-bouncy riders.
|
|
767
|
+
Round obstacles, mutual push bodies and pool damping are in
|
|
768
|
+
[Shaped solids](patterns.md#shaped-solids-ramps-round-posts-and-bodies-that-push-back--slopesjs-circlesjs-pooljs-drumjs).
|
|
638
769
|
|
|
639
770
|
**Opponent AI** on an 80 ms timer, reading a handful of live properties per tick:
|
|
640
771
|
|