@maccesar/titools 4.19.0 → 4.21.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 +11 -6
- package/agents/ti-pro.md +3 -0
- package/commands/ti-audit.md +11 -10
- package/commands/ti-new-screen.md +1 -0
- package/lib/config.js +1 -0
- package/package.json +1 -1
- package/skills/purgetss/SKILL.md +1 -1
- package/skills/purgetss/references/cli-commands.md +25 -6
- package/skills/purgetss/references/customization-deep-dive.md +9 -4
- package/skills/purgetss/references/multi-density-images.md +35 -5
- package/skills/purgetss/references/version-history.md +5 -0
- package/skills/ti-expert/SKILL.md +5 -2
- package/skills/ti-expert/references/patterns.md +2 -0
- package/skills/ti-game/SKILL.md +5 -2
- package/skills/ti-game/references/api.md +38 -4
- package/skills/ti-game/references/debugging-performance.md +1 -1
- package/skills/ti-game/references/examples.md +6 -5
- package/skills/ti-game/references/patterns.md +1 -1
- package/skills/ti-game/references/project-setup.md +7 -3
- package/skills/ti-game/references/recipes-action.md +1 -1
- package/skills/ti-game/references/recipes-adventure-effects.md +1 -1
- package/skills/ti-game/references/roadmap.md +8 -4
- package/skills/ti-game/references/tilemaps.md +1 -1
- package/skills/ti-reuse-first/SKILL.md +88 -0
- package/skills/ti-reuse-first/references/alloy-reuse.md +127 -0
- package/skills/ti-reuse-first/references/diagnostics.md +137 -0
- package/skills/ti-reuse-first/references/indirection.md +89 -0
- package/skills/ti-reuse-first/references/purgetss-reuse.md +84 -0
- package/skills/ti-reuse-first/references/the-ladder.md +123 -0
- package/skills/ti-ui/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
TiTools is a Titanium SDK toolkit for AI coding assistants. It ships
|
|
9
|
+
TiTools is a Titanium SDK toolkit for AI coding assistants. It ships 11 skills — 6 specialist skills (`ti-reuse-first`, `ti-expert`, `purgetss`, `ti-ui`, `ti-game`, `ti-synthengine`) plus 5 documentation-mirror skills (`ti-api`, `ti-guides`, `ti-howtos`, `alloy-guides`, `alloy-howtos`) — a research agent, and reference files covering reuse and simplification, Titanium architecture, API reference, native how-tos, Alloy MVC, PurgeTSS styling and resource automation, UI/UX patterns, 2D games, and native sound synthesis.
|
|
10
10
|
|
|
11
11
|
The reference files are maintained against official documentation whenever an official source exists, so the assistant can retrieve current framework behavior instead of guessing from generic training data.
|
|
12
12
|
|
|
@@ -36,7 +36,7 @@ The fastest way to get started with Claude Code. One command to add the marketpl
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
What you get:
|
|
39
|
-
- All
|
|
39
|
+
- All 11 TiTools skills (`ti-reuse-first`, `ti-expert`, `purgetss`, `ti-ui`, `ti-game`, `ti-synthengine`, `ti-api`, `ti-guides`, `ti-howtos`, `alloy-guides`, `alloy-howtos`)
|
|
40
40
|
- ti-pro research agent
|
|
41
41
|
- Session hook that auto-detects Titanium projects (`tiapp.xml`, Alloy, PurgeTSS, `ti.game`, `ti.synthengine`)
|
|
42
42
|
- Slash commands: `/ti-check`, `/ti-new-screen`, `/ti-audit`
|
|
@@ -174,6 +174,7 @@ The knowledge index is based on the latest Titanium SDK documentation. If your p
|
|
|
174
174
|
|
|
175
175
|
| Skill | Purpose | Best For |
|
|
176
176
|
| ------------ | ----------------------------------- | --------------------------------------- |
|
|
177
|
+
| ti-reuse-first | Reuse before writing new code | Whether it should exist and what already does it; duplication sweeps |
|
|
177
178
|
| ti-expert | Architecture and implementation | Starting point for most tasks |
|
|
178
179
|
| purgetss | Alloy styling + asset automation | Utility classes; Alloy/Classic branding, images, colors, fonts and modules |
|
|
179
180
|
| ti-ui | UI/UX patterns | Complex layouts, ListViews, platform UI |
|
|
@@ -186,7 +187,8 @@ The knowledge index is based on the latest Titanium SDK documentation. If your p
|
|
|
186
187
|
| alloy-howtos | Alloy CLI, configuration, debugging | alloy.jmk, config.json, custom XML tags |
|
|
187
188
|
|
|
188
189
|
Notes:
|
|
189
|
-
- The first
|
|
190
|
+
- The first six (`ti-reuse-first`, `ti-expert`, `purgetss`, `ti-ui`, `ti-game`, `ti-synthengine`) are specialist workflow skills reflecting TiTools' Titanium conventions and native-module contracts.
|
|
191
|
+
- `ti-reuse-first` runs before the others. It asks whether a thing needs to exist and what already does it — a search order that stops at the first rung that holds — and sweeps an existing `app/` for near-identical controllers, single-listener events, wrappers around one call, and layout recomputed in JavaScript. It is deliberately two-sided: it says when adding a module or a Widget is the right answer, so the bias does not simply invert.
|
|
190
192
|
- `ti-game` documents the `ti.game` native module (2D sprite engine on OpenGL ES 2.0, Android and iOS). Its API reference was verified against the module source and tracks upstream `main` (the module ships features ahead of its manifest version); it applies to Alloy and Classic projects alike and does not depend on PurgeTSS.
|
|
191
193
|
- `ti-synthengine` translates aesthetic sound requests into strict, production-ready JavaScript for the cross-platform `ti.synthengine` 1.0.0 module. Seven references preserve the official documentation and an eighth adds curated retro, xylophone, memory-pad, polyphony and timer-ownership recipes; the API contract was cross-checked against both native implementations.
|
|
192
194
|
- The latter five are documentation-mirror skills sourced from the official Titanium SDK docs (`tidev/titanium-docs`) and audited against them via the internal `titools-skill-auditor`.
|
|
@@ -249,7 +251,7 @@ TiTools provides slash commands for common tasks. Both channels ship them: the p
|
|
|
249
251
|
These commands automatically invoke the relevant skills:
|
|
250
252
|
- `/ti-check` reads `tiapp.xml`, validates declared native modules, and reports project health
|
|
251
253
|
- `/ti-new-screen` uses `ti-expert` + `purgetss` to create properly structured files
|
|
252
|
-
- `/ti-audit` routes all
|
|
254
|
+
- `/ti-audit` routes all 11 skills: the reuse sweep first, core Titanium checks always, Alloy/PurgeTSS checks when detected, integration how-tos when used, and `ti-game` / `ti-synthengine` checks when those modules are declared
|
|
253
255
|
|
|
254
256
|
---
|
|
255
257
|
|
|
@@ -289,6 +291,7 @@ All skills include automatic project detection to ensure compatibility:
|
|
|
289
291
|
| Skill | What It Detects | How It Works |
|
|
290
292
|
| --------- | --------------------- | ------------------------------------------------------------- |
|
|
291
293
|
| purgetss | PurgeTSS mode and project type | Checks PurgeTSS markers, then separates Alloy utilities from Classic standalone commands |
|
|
294
|
+
| ti-reuse-first | Existing app structure | Searches `app/lib/`, `app/controllers/`, `app/widgets/`, `purgetss/config.cjs` before proposing anything new |
|
|
292
295
|
| ti-expert | Alloy vs Classic | Checks for `app/` (Alloy) vs `Resources/` (Classic) structure |
|
|
293
296
|
| ti-ui | Titanium projects | Checks for `tiapp.xml` (both Alloy and Classic) |
|
|
294
297
|
| ti-game | ti.game module | Checks for `<module>ti.game</module>` in `tiapp.xml` or `require('ti.game')` |
|
|
@@ -301,16 +304,17 @@ Why this matters:
|
|
|
301
304
|
|
|
302
305
|
### Skill hierarchy
|
|
303
306
|
|
|
304
|
-
`ti-expert` is the architectural starting point
|
|
307
|
+
`ti-reuse-first` runs first — it decides whether there is anything to build — and `ti-expert` is the architectural starting point for what survives that question. Together they coordinate the rest of the bundled catalog:
|
|
305
308
|
|
|
306
309
|
| Scope | Skills |
|
|
307
310
|
| --- | --- |
|
|
311
|
+
| Whether to build it at all | `ti-reuse-first`, first |
|
|
308
312
|
| Architecture and UI | `ti-expert`, `ti-ui`, `purgetss` when configured |
|
|
309
313
|
| Native modules | `ti-game`, `ti-synthengine` when declared |
|
|
310
314
|
| Exact SDK/API guidance | `ti-api`, `ti-guides`, `ti-howtos` |
|
|
311
315
|
| Alloy MVC and tooling | `alloy-guides`, `alloy-howtos` for Alloy projects |
|
|
312
316
|
|
|
313
|
-
All
|
|
317
|
+
All eleven ship with TiTools; no second skill package is required.
|
|
314
318
|
|
|
315
319
|
---
|
|
316
320
|
|
|
@@ -804,6 +808,7 @@ This pattern is documented across four bundled TiTools skills: `ti-ui`, `ti-expe
|
|
|
804
808
|
|
|
805
809
|
| Skill | SKILL.md | References |
|
|
806
810
|
| --------- | ----------------------------- | ----------------------------------------------------- |
|
|
811
|
+
| ti-reuse-first | Reuse Before You Write | 5 files (the ladder, Alloy reuse, indirection, PurgeTSS reuse, diagnostics) |
|
|
807
812
|
| ti-expert | Architecture + Implementation | 26 files (patterns, feedback surfaces, file type association, sharing, testing, security, etc.) |
|
|
808
813
|
| purgetss | Setup + Critical Rules | 34 files (Classic compatibility, grid, animations, icons, class-index, SVG pipeline, etc.) |
|
|
809
814
|
| ti-ui | UI Rules + Platform Diffs | 14 files (layouts, lists, gestures, etc.) |
|
package/agents/ti-pro.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: ti-pro
|
|
3
3
|
description: Titanium SDK and Alloy research specialist. Loads the complete TiTools skill catalog and uses the relevant scopes together for research-style questions. Use for codebase analysis, architecture reviews, Titanium SDK implementation research, cross-feature questions, native game or sound modules, and platform-specific differences. Returns concrete findings with file and line references.
|
|
4
4
|
skills:
|
|
5
|
+
- ti-reuse-first
|
|
5
6
|
- ti-expert
|
|
6
7
|
- purgetss
|
|
7
8
|
- ti-ui
|
|
@@ -24,6 +25,7 @@ You are a research specialist for Titanium SDK and Alloy mobile development. Wit
|
|
|
24
25
|
|
|
25
26
|
When invoked, research complex topics by consulting the applicable skills from the complete TiTools catalog:
|
|
26
27
|
|
|
28
|
+
- `ti-reuse-first` - Whether the thing should exist at all, and what already does it; duplication and dead-structure sweeps
|
|
27
29
|
- `ti-expert` - Architecture, patterns, conventions
|
|
28
30
|
- `purgetss` - Utility-first styling classes (optional add-on; use when the project already has it or the user asks about it)
|
|
29
31
|
- `ti-ui` - UI/UX patterns, layouts, gestures
|
|
@@ -56,6 +58,7 @@ Do not force conditional skills onto an unrelated project. State which scopes we
|
|
|
56
58
|
|
|
57
59
|
| Use Instead | Reason |
|
|
58
60
|
| -------------------------------------------- | -------------------------------------- |
|
|
61
|
+
| `/ti-reuse-first` before writing new code | Inline reuse check during development |
|
|
59
62
|
| `/ti-expert` for architecture guidance | Inline consultation during development |
|
|
60
63
|
| `/purgetss` to verify a class | Quick inline reference |
|
|
61
64
|
| `/ti-ui` for UI/UX patterns and layouts | Task-oriented guidance |
|
package/commands/ti-audit.md
CHANGED
|
@@ -7,22 +7,23 @@ Audit the current Titanium project by invoking every applicable TiTools skill. F
|
|
|
7
7
|
|
|
8
8
|
Core Titanium audit:
|
|
9
9
|
|
|
10
|
-
1. Invoke `ti-
|
|
11
|
-
2. Invoke `ti-
|
|
12
|
-
3. Invoke `ti-
|
|
13
|
-
4. Invoke `ti-
|
|
14
|
-
5. Invoke `ti-
|
|
10
|
+
1. Invoke `ti-reuse-first` — before judging how anything is built, establish whether it should exist and what already does it: near-identical controllers, panels copied instead of parameterized, events with a single listener, wrappers around one call, repeated class strings, and layout recomputed in JavaScript that TSS resolves declaratively. Report each finding with the command that measured it, and with what consolidating it would cost as well as save — duplication kept on purpose is a valid answer.
|
|
11
|
+
2. Invoke `ti-expert` — check architecture, navigation ownership, event/timer cleanup, memory leaks, bridge pressure, testing boundaries, adaptive layouts and code conventions.
|
|
12
|
+
3. Invoke `ti-guides` — check `tiapp.xml`, CommonJS/Hyperloop usage, Titanium CLI/build assumptions, memory and bridge guidance, distribution settings, and toolchain compatibility.
|
|
13
|
+
4. Invoke `ti-api` — verify every audited `Ti.*` API, event, property, constant and third-party module call against its exact signature. Do not report a suspected API defect from memory.
|
|
14
|
+
5. Invoke `ti-ui` when the project creates Titanium UI — check layouts, list performance, event bubbling, gestures, animations, accessibility, orientation and platform-specific UI.
|
|
15
|
+
6. Invoke `ti-howtos` when the project implements a native integration such as location, maps, push, camera, media, SQLite, HTTPClient, WebView, intents, Keychain/iCloud, Hyperloop or CI/CD — audit the complete integration and platform setup, not only the JavaScript call site.
|
|
15
16
|
|
|
16
17
|
Project-type audit:
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
7. If the project is Alloy (`app/views/`, `app/controllers/`, `app/styles/`), invoke `alloy-guides` — check controllers, XML/TSS, models, collections, widgets, sync adapters and cleanup patterns.
|
|
20
|
+
8. If the Alloy project uses `alloy.jmk`, `config.json`, `widget.json`, conditional views, custom XML tags or Alloy CLI workflows, invoke `alloy-howtos` — check configuration precedence, build hooks, generated structures and compilation/debugging conventions.
|
|
21
|
+
9. If the project uses PurgeTSS (`purgetss/`, `purgetss/config.cjs`, a PurgeTSS-generated `app.tss`, or `purgetss.{ui,colors,fonts}.js`), invoke `purgetss` and detect Alloy vs. Classic before applying findings. In Alloy, audit unsupported utility classes, padding on Views, unnecessary manual TSS, dynamic components, grids, icons and animations. In Classic, audit only the supported standalone asset/CommonJS commands and their `Resources/` outputs; do not require or recommend `alloy.jmk`, generated TSS, utility classes, or `$.UI.create()`.
|
|
21
22
|
|
|
22
23
|
Native-module audit:
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
10. If the project declares `ti.game`, invoke `ti-game` — check for sprites moved from timers instead of native properties, levels sized from `Ti.Platform.displayCaps` instead of the `resize` event, sprites added one by one instead of in one array, and timers or sounds left running when the window closes.
|
|
26
|
+
11. If the project declares `ti.synthengine`, invoke `ti-synthengine` — check strict option names and types, mutually exclusive pitch fields, valid envelope intersections, checked Boolean results, one clear lifecycle owner, delayed startup after layout, app-owned timer cancellation, and `shutdown()` cleanup.
|
|
26
27
|
|
|
27
28
|
If a conditional skill does not apply, record it as `not applicable` with the detected reason instead of inventing findings from it.
|
|
28
29
|
|
|
@@ -6,6 +6,7 @@ argument-hint: "<screenName>"
|
|
|
6
6
|
|
|
7
7
|
Create a new Alloy screen with the given name. Before creating files:
|
|
8
8
|
|
|
9
|
+
0. Invoke the `ti-reuse-first` skill and confirm the screen has to be new. Run rung 1 against this project: list `app/controllers/` and `app/widgets/` and check whether an existing screen covers the case with a parameter, or whether a `<Require>` into an existing window is enough. If the answer is that an existing screen should be parameterized instead, say so, name it, and **stop** — do not generate the trio. Only continue when a new screen is genuinely the answer.
|
|
9
10
|
1. Invoke the `alloy-guides` skill to follow Alloy MVC patterns
|
|
10
11
|
2. If PurgeTSS is detected (purgetss/ folder exists), invoke the `purgetss` skill — all styling MUST use utility classes in the XML, NOT custom TSS
|
|
11
12
|
3. If PurgeTSS is detected, invoke the `ti-ui` skill for layout patterns
|
package/lib/config.js
CHANGED
package/package.json
CHANGED
package/skills/purgetss/SKILL.md
CHANGED
|
@@ -468,7 +468,7 @@ Load these only when needed:
|
|
|
468
468
|
- [PurgeTSS UI in Classic](references/purgetss-ui-classic.md) - Native JavaScript setup, public exports, animation objects, cleanup, and platform notes
|
|
469
469
|
|
|
470
470
|
### Release Notes
|
|
471
|
-
- [Version History](references/version-history.md) - Release-by-release feature additions and behavior changes (v7.4.0 → v7.
|
|
471
|
+
- [Version History](references/version-history.md) - Release-by-release feature additions and behavior changes (v7.4.0 → v7.17.0)
|
|
472
472
|
|
|
473
473
|
> **💡 TEXT FONTS (Google Fonts, Roboto, etc.)**
|
|
474
474
|
> For text fonts, see [Custom Fonts](references/custom-fonts.md).
|
|
@@ -86,10 +86,16 @@ module.exports = {
|
|
|
86
86
|
splashIcon: { enabled: false }, notificationIcon: { enabled: false },
|
|
87
87
|
ninePatch: { enabled: false }
|
|
88
88
|
},
|
|
89
|
+
// Sources in purgetss/images/ are 4x masters: a 1024px file yields
|
|
90
|
+
// 256 (mdpi/@1x), 384 (hdpi), 512 (xhdpi/@2x), 768 (xxhdpi/@3x), 1024 (xxxhdpi).
|
|
91
|
+
// There is no width to configure here — the source's own pixels decide.
|
|
92
|
+
// SVGs have no natural pixels; pin theirs in files: [] below.
|
|
89
93
|
images: {
|
|
90
|
-
quality: 85, //
|
|
94
|
+
quality: 85, // webp/jpeg/avif/tiff quality (0-100); PNG and GIF ignore it
|
|
91
95
|
format: null, // null = keep original; 'webp' | 'jpeg' | 'png' to convert every image
|
|
92
|
-
|
|
96
|
+
autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files
|
|
97
|
+
confirmOverwrites: true, // prompt before overwriting files (set false to skip)
|
|
98
|
+
files: [] // per-file overrides: [{ filename: 'images/<sub>/<name>.<ext>', width, height? }]
|
|
93
99
|
},
|
|
94
100
|
theme: {
|
|
95
101
|
extend: {}
|
|
@@ -171,7 +177,7 @@ Recommended VSCode extensions:
|
|
|
171
177
|
|
|
172
178
|
### Commands Executed Internally
|
|
173
179
|
|
|
174
|
-
`purgetss create "Name of the Project" [--dependencies --vendor=fa,mi,ms,f7]` runs: `ti config` (reads idprefix/workspace), `ti create -t app -p all -n "Name"`, `alloy new`, `purgetss w`, `purgetss b`, optional `--vendor` (copies fonts + CommonJS module), optional `--dependencies` (installs Tailwind CSS
|
|
180
|
+
`purgetss create "Name of the Project" [--dependencies --vendor=fa,mi,ms,f7]` runs: `ti config` (reads idprefix/workspace), `ti create -t app -p all -n "Name"`, `alloy new`, `purgetss w`, `purgetss b`, optional `--vendor` (copies fonts + CommonJS module), optional `--dependencies` (installs Tailwind CSS and ESLint, and copies the config files — see [`install-dependencies`](#install-dependencies-command)), then opens the project in VS Code, Sublime Text, or Finder.
|
|
175
181
|
|
|
176
182
|
## `brand` Command
|
|
177
183
|
|
|
@@ -331,7 +337,7 @@ purgetss images background/ # re-process one subfolder
|
|
|
331
337
|
| `--android` | Only emit Android density variants. Mutually exclusive with `--ios`. |
|
|
332
338
|
| `--ios` | Only emit iPhone scale variants. Mutually exclusive with `--android`. |
|
|
333
339
|
| `--format <ext>` | Convert all outputs to `webp`, `jpeg`, `png`, `avif`, `gif`, or `tiff`. Default: keep source format. |
|
|
334
|
-
| `--quality <n>` | Quality `0-100` for
|
|
340
|
+
| `--quality <n>` | Quality `0-100` for `webp`, `jpeg`, `avif` and `tiff`. PNG and GIF ignore it. Default `85`. |
|
|
335
341
|
| `--width <n>` | (v7.8.0) Pin Android `mdpi` (= iPhone `@1x`) to `<n>` pixels wide. Larger scales derive as ×1.5, ×2, ×3, ×4 from that base; height stays proportional to the source's aspect ratio. Integer in `[1, 8192]`. |
|
|
336
342
|
| `--opacity <n>` | (v7.10.0) Multiply alpha of every density by `n/100`. Integer in `[0, 100]`. With `--format jpeg`, alpha flattens on white. |
|
|
337
343
|
| `--padding <n>` | (v7.10.0) Shrink the rendered image inside each density canvas by `n%` symmetric borders. Integer in `[0, 40]`. |
|
|
@@ -369,12 +375,16 @@ Defaults live under `images:` in `purgetss/config.cjs`:
|
|
|
369
375
|
|
|
370
376
|
```javascript
|
|
371
377
|
images: {
|
|
372
|
-
quality: 85, //
|
|
378
|
+
quality: 85, // webp/jpeg/avif/tiff quality (0-100); PNG and GIF ignore it
|
|
373
379
|
format: null, // null = keep original; 'webp' | 'jpeg' | 'png' | 'avif' | 'gif' | 'tiff'
|
|
374
|
-
|
|
380
|
+
autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files
|
|
381
|
+
confirmOverwrites: true, // prompt before overwriting files
|
|
382
|
+
files: [] // per-file overrides: [{ filename, width, height? }]
|
|
375
383
|
}
|
|
376
384
|
```
|
|
377
385
|
|
|
386
|
+
Those five keys are the whole section. Since v7.17.0 any other key — including `width`, `opacity`, `padding` and `output`, which are CLI flags — aborts the run before writing anything, and so does a `files[]` entry with an unknown key or no `filename`. See [Unknown keys are an error](./multi-density-images.md#unknown-keys-are-an-error-v7170).
|
|
387
|
+
|
|
378
388
|
Like `brand`, `images` writes in place and asks `Continue? [y/N/a]` before overwriting. Selecting `a` flips `confirmOverwrites: false` in `config.cjs`. Skipped automatically when `stdin` is not a TTY, when `-y`/`--yes` is passed, or when `PURGETSS_YES=1` is set.
|
|
379
389
|
|
|
380
390
|
### Examples
|
|
@@ -481,6 +491,15 @@ purgetss install-dependencies
|
|
|
481
491
|
purgetss id
|
|
482
492
|
```
|
|
483
493
|
|
|
494
|
+
### What it installs (v7.17.0)
|
|
495
|
+
|
|
496
|
+
`npm i -D eslint @eslint/js` plus a flat `eslint.config.mjs` in the project root, `.editorconfig`, `.vscode/{extensions,settings}.json` when the `code` command exists, and `tailwindcss@3` for IntelliSense. `create --dependencies` runs the same ESLint and Tailwind steps. `eslint-config-axway` and `eslint-plugin-alloy` are no longer installed.
|
|
497
|
+
|
|
498
|
+
The shipped config declares the Titanium and Alloy globals itself (`Ti`, `Titanium`, `Alloy`, `Backbone`, `$`, `$model`, `_`, `L`, `Widget`, `OS_*`, `ENV_*`, `DIST_*`, `console`, `alert`, the timer functions, and `task` for `alloy.jmk`). It lints `app/**/*.js` and ignores `Resources/`, `build/`, `node_modules/`, `purgetss/`, plus the six libraries PurgeTSS copies into `app/lib/`, listed by name so the developer's own files there keep being linted. `no-unused-vars` is a **warning**: Alloy wires handlers from the XML view, so a controller function with no caller in the JS may still be in use.
|
|
499
|
+
|
|
500
|
+
> **Warning**
|
|
501
|
+
> Before v7.17.0 this command shipped an `.eslintrc.js` extending `eslint-config-axway/env-alloy` and installed `eslint-plugin-alloy`. That setup cannot run under ESLint 9 — it stopped reading `.eslintrc.*`, axway removed `env-alloy` in `eslint-config-axway@10.0.0`, and the unmaintained plugin throws `This method cannot be used with flat config`. Projects scaffolded between December 2025 and v7.17.0 have a lint that never ran; re-run `purgetss install-dependencies` to replace it.
|
|
502
|
+
|
|
484
503
|
> **Caution**
|
|
485
504
|
> This command overwrites any existing `extensions.json` and `settings.json` files. Back them up if you want to keep your current versions.
|
|
486
505
|
|
|
@@ -75,14 +75,19 @@ module.exports = {
|
|
|
75
75
|
notificationIcon: { enabled: false }, // drawable-*/ic_stat_notify.png × 5
|
|
76
76
|
ninePatch: { enabled: false } // background.9.png (not implemented yet)
|
|
77
77
|
},
|
|
78
|
+
// Sources in purgetss/images/ are 4x masters: a 1024px file yields
|
|
79
|
+
// 256 (mdpi/@1x), 384 (hdpi), 512 (xhdpi/@2x), 768 (xxhdpi/@3x), 1024 (xxxhdpi).
|
|
80
|
+
// There is no width to configure here — the source's own pixels decide.
|
|
81
|
+
// SVGs have no natural pixels; pin theirs in files: [] below.
|
|
78
82
|
images: {
|
|
79
|
-
|
|
80
|
-
quality: 85, // JPEG/WebP/AVIF quality (0-100)
|
|
83
|
+
quality: 85, // webp/jpeg/avif/tiff quality (0-100); PNG and GIF ignore it
|
|
81
84
|
format: null, // null = keep original; 'webp' | 'jpeg' | 'png' to convert every image
|
|
85
|
+
autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files
|
|
82
86
|
confirmOverwrites: true, // prompt before overwriting files (set false to skip)
|
|
83
|
-
files: [] // per-file overrides: [{ filename, width, height? }]
|
|
87
|
+
files: [] // per-file overrides: [{ filename: 'images/<sub>/<name>.<ext>', width, height? }]
|
|
84
88
|
// Note: --width (v7.8.0) and --opacity / --padding / --output (v7.10.0) are CLI-only
|
|
85
|
-
// by design — those decisions are per-asset, not project-wide.
|
|
89
|
+
// by design — those decisions are per-asset, not project-wide. Since v7.17.0
|
|
90
|
+
// writing any of them here aborts the run instead of being ignored.
|
|
86
91
|
},
|
|
87
92
|
theme: {
|
|
88
93
|
extend: {}
|
|
@@ -189,25 +189,55 @@ This is a hint, **not an error**. The legacy 4×-from-viewBox behavior still run
|
|
|
189
189
|
On the first run, `purgetss images` injects an `images:` block into your existing `purgetss/config.cjs` (between `brand:` and `theme:`) with these defaults:
|
|
190
190
|
|
|
191
191
|
```javascript
|
|
192
|
+
// Sources in purgetss/images/ are 4x masters: a 1024px file yields
|
|
193
|
+
// 256 (mdpi/@1x), 384 (hdpi), 512 (xhdpi/@2x), 768 (xxhdpi/@3x), 1024 (xxxhdpi).
|
|
194
|
+
// There is no width to configure here — the source's own pixels decide.
|
|
195
|
+
// SVGs have no natural pixels; pin theirs in files: [] below.
|
|
192
196
|
images: {
|
|
193
|
-
|
|
194
|
-
quality: 85, // JPEG/WebP/AVIF quality (0-100)
|
|
197
|
+
quality: 85, // webp/jpeg/avif/tiff quality (0-100); PNG and GIF ignore it
|
|
195
198
|
format: null, // null = keep original; 'webp' | 'jpeg' | 'png' to convert every image
|
|
199
|
+
autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files
|
|
196
200
|
confirmOverwrites: true, // prompt before overwriting files (set false to skip)
|
|
197
201
|
files: [] // per-file overrides: [{ filename: 'images/<sub>/<name>.<ext>', width, height? }]
|
|
198
202
|
}
|
|
199
203
|
```
|
|
200
204
|
|
|
205
|
+
Since v7.17.0 the four comment lines above the section ship with the block itself, and the same text seeds through `init`. Reproduce the key order above when writing a config by hand — it is the order both generated copies use.
|
|
206
|
+
|
|
201
207
|
| Key | Default | Purpose |
|
|
202
208
|
| --- | --- | --- |
|
|
203
|
-
| `
|
|
204
|
-
| `quality` | `85` | Quality for lossy formats (JPEG, WebP, AVIF). Range `0–100`. |
|
|
209
|
+
| `quality` | `85` | Range `0–100`. Reaches `webp`, `jpeg`, `avif` and `tiff` only. PNG is written with `compressionLevel: 9` and GIF takes no quality parameter, so the value never applies to either. |
|
|
205
210
|
| `format` | `null` | `null` keeps each source's original format. Set `'webp'`, `'jpeg'`, or `'png'` to convert every output. |
|
|
211
|
+
| `autoSync` | `true` | When `false`, the SVG pipeline still computes dimensions and generates PNGs but never writes back to `images.files`. See [SVG-aware compile-time pipeline](./svg-pipeline.md). |
|
|
206
212
|
| `confirmOverwrites` | `true` | When `false`, the `[y/N/a]` prompt is skipped. |
|
|
207
213
|
| `files` | `[]` | Per-file dimension overrides. See [Per-file overrides](#per-file-overrides-with-imagesfiles). |
|
|
208
214
|
|
|
209
215
|
Change whatever you want to override globally; CLI flags still win for one-off runs. `autoSync` and `files` arrived in v7.11.0 to support the [SVG-aware compile-time pipeline](./svg-pipeline.md).
|
|
210
216
|
|
|
217
|
+
### Unknown keys are an error (v7.17.0)
|
|
218
|
+
|
|
219
|
+
Those five keys are the entire section. Since v7.17.0 anything else aborts the run before a single file is written, the same validation `brand:` has had since v7.13.0:
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
> purgetss images
|
|
223
|
+
Error running images: Unknown key(s) in the images: section of purgetss/config.cjs:
|
|
224
|
+
• images.qualty
|
|
225
|
+
• images.files[0].widht
|
|
226
|
+
|
|
227
|
+
Top-level keys: quality, format, autoSync, confirmOverwrites, files
|
|
228
|
+
Inside files[]: filename, width, height
|
|
229
|
+
|
|
230
|
+
Check the spelling. No images were generated.
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
A `qualty: 95` that gets ignored is indistinguishable from the default, so before v7.17.0 the typo produced the wrong output silently. The check runs at both levels and reports every problem in one pass:
|
|
234
|
+
|
|
235
|
+
- **Top level** — only `quality`, `format`, `autoSync`, `confirmOverwrites`, `files`.
|
|
236
|
+
- **Inside each `files[]` entry** — only `filename`, `width`, `height`. `filename` is **required** and must be a string; an entry without one matches no file and used to be skipped in the same silence.
|
|
237
|
+
- `files` itself must be an array, and each entry an object.
|
|
238
|
+
|
|
239
|
+
Do not suggest `width`, `opacity`, `padding` or `output` as `images:` keys. They are CLI flags and were never part of the section — the validator now names them as errors instead of ignoring them. When one file needs a fixed width, pin it in `files`; the other three describe a single run.
|
|
240
|
+
|
|
211
241
|
## Per-file overrides with `images.files`
|
|
212
242
|
|
|
213
243
|
`images.files` (v7.11.0) is an array of per-file dimension pins. It does **not** filter which images get processed — `purgetss images` always walks every supported file under `purgetss/images/`. Entries in `files` only override the source and derived sizing for files whose `filename` matches.
|
|
@@ -484,7 +514,7 @@ If you only tweaked CSS classes (no image changes), you don't need to re-run `pu
|
|
|
484
514
|
| Flag | Purpose |
|
|
485
515
|
| --- | --- |
|
|
486
516
|
| `--format <ext>` | Convert all outputs to: `webp`, `jpeg`, `png`, `avif`, `gif`, `tiff`. Default: keep source format. |
|
|
487
|
-
| `--quality <n>` | Quality `0–100` for
|
|
517
|
+
| `--quality <n>` | Quality `0–100` for `webp`, `jpeg`, `avif` and `tiff`. PNG and GIF ignore it. Default `85`. |
|
|
488
518
|
| `--width <n>` | (v7.8.0) Pin `mdpi` / `@1x` output width to `n` pixels; `[1, 8192]`. Other densities derive from this base (×1.5 / ×2 / ×3 / ×4). Most useful for SVG sources with non-standard viewBoxes. CLI-only — no `config.cjs` equivalent because width is per-asset. |
|
|
489
519
|
| `--opacity <n>` | (v7.10.0) Multiply the alpha channel of every generated density by `n/100`. Range `[0, 100]`. Combine with `--format jpeg` and the alpha is flattened on white instead of producing a transparent JPEG. CLI-only. |
|
|
490
520
|
| `--padding <n>` | (v7.10.0) Shrink the rendered image inside each density canvas by `n%` symmetric borders. Range `[0, 40]`. CLI-only. |
|
|
@@ -8,6 +8,11 @@ When in doubt about whether a class, flag, or config key exists in the user's in
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## v7.17.0
|
|
12
|
+
- The `images:` section rejects unknown keys instead of ignoring them, at both levels: the five top-level keys (`quality`, `format`, `autoSync`, `confirmOverwrites`, `files`) and the three inside each `files[]` entry (`filename`, `width`, `height`, with `filename` required). Nothing is generated when validation fails. Never suggest `width`, `opacity`, `padding` or `output` as config keys — they are CLI flags and now abort the run. See [multi-density-images.md](multi-density-images.md).
|
|
13
|
+
- The generated `images:` block gained four comment lines explaining the 4× master convention, and the `quality` comment now names the formats it reaches: `webp`, `jpeg`, `avif`, `tiff`. PNG uses `compressionLevel: 9` and GIF takes no quality parameter, so neither is affected. Both copies changed — the one patched into an existing config and the one `init` seeds.
|
|
14
|
+
- `install-dependencies` and `create --dependencies` now install only `eslint` and `@eslint/js` and copy a flat `eslint.config.mjs`. `eslint-config-axway` and `eslint-plugin-alloy` are gone: the old `.eslintrc.js` could not run under ESLint 9. See [cli-commands.md](cli-commands.md#install-dependencies-command).
|
|
15
|
+
|
|
11
16
|
## v7.16.2
|
|
12
17
|
- `build-fonts --module` now exports every processed TTF/OTF PostScript name through `families`, including text-only collections; icon CSS additionally populates `icons`. Filename-derived keys such as `poppinsSemiBold` are supported. See [custom-fonts.md](custom-fonts.md).
|
|
13
18
|
- In clean Classic projects, saved `shades` palettes and `color-module` no longer scaffold empty brand, font, or image source folders. `shades` still refreshes an existing generated color module. See [classic-projects.md](classic-projects.md).
|
|
@@ -9,12 +9,15 @@ allowed-tools: Read, Grep, Glob, Edit, Write, Bash(git *), Bash(node *)
|
|
|
9
9
|
|
|
10
10
|
Practical architecture and implementation guidance for Titanium SDK apps (Alloy and Classic). Focus on maintainability, clear boundaries, and low-friction testing.
|
|
11
11
|
|
|
12
|
+
> **Before anything new exists, run the `ti-reuse-first` skill.** It answers *whether* a controller, view, Widget, `app/lib/` module, event or wrapper should exist at all and what already does it; this guide answers *how* to build whatever survives that question. Copying an existing controller as a template counts as creating something new.
|
|
13
|
+
|
|
12
14
|
## Required workflow
|
|
13
15
|
|
|
14
16
|
This file is an index. Read the references that govern the task before making a recommendation or changing code.
|
|
15
17
|
|
|
16
18
|
| Task | Required reading |
|
|
17
19
|
| --- | --- |
|
|
20
|
+
| Anything new — a controller/view/style trio, Widget, `app/lib/` module, event, wrapper, or a file copied as a template | Run the `ti-reuse-first` skill first, then the row below that matches what survives |
|
|
18
21
|
| Architecture tier, folders, or boundaries | [Architecture tiers](references/architecture-tiers.md), [Alloy structure](references/alloy-structure.md) |
|
|
19
22
|
| Feedback, alerts, choices, or app-owned versus system UI | [Feedback surfaces](references/feedback-surfaces.md) |
|
|
20
23
|
| Reusable Snackbar, Dialog, Bottom Sheet, Widget, or `<Require>` | [Feedback Widget contracts](references/feedback-widget-contracts.md), [Alloy structure](references/alloy-structure.md) |
|
|
@@ -151,14 +154,14 @@ For the complete reference with examples, see [Alloy builtins and globals](refer
|
|
|
151
154
|
| ---------------------------------- | -------------------------------------------------------------- |
|
|
152
155
|
| How to create a new Alloy project? | `ti create -t app --alloy` (not `--classic` + `alloy new`) |
|
|
153
156
|
| Fastest way to build? | `tn <recipe>` (using TiNy CLI wrapper) |
|
|
154
|
-
| Controller > 100 lines? |
|
|
157
|
+
| Controller > 100 lines? | A smell, not a threshold — see `ti-reuse-first` |
|
|
155
158
|
| More than 50 IDs in XML? | Use Tier 3 (ID Scoping) |
|
|
156
159
|
| Where does API call go? | `lib/api/` |
|
|
157
160
|
| Where does business logic go? | `lib/services/` |
|
|
158
161
|
| How deep should `lib` folders be? | One level: `lib/<type>/<file>.js` |
|
|
159
162
|
| Where do I store auth tokens? | Keychain (iOS) / KeyStore (Android) via service |
|
|
160
163
|
| Models or Collections? | Collections for API data, Models for SQLite persistence |
|
|
161
|
-
| Ti.App.fireEvent or EventBus? |
|
|
164
|
+
| Ti.App.fireEvent or EventBus? | EventBus — once `ti-reuse-first` says it needs an event |
|
|
162
165
|
| Direct navigation or service? | Always Navigation service (auto cleanup) |
|
|
163
166
|
| Snackbar, Dialog, or Bottom Sheet? | Classify urgency, persistence, reversibility, and choice first |
|
|
164
167
|
| Widget or `<Require>`? | Widget for a self-contained API/lifecycle; `<Require>` for app composition |
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
<!-- TOC-END -->
|
|
17
17
|
|
|
18
|
+
> **Before introducing any of these:** each pattern below adds a layer, and a layer only pays for itself where there is a boundary — a place the implementation could plausibly change, or where the two sides genuinely should not know each other. Around a single call site with one caller, the same pattern is a detour that costs on every read. The `ti-reuse-first` skill runs that check; use it first, and treat the patterns here as the answer to *how* once it has established *whether*.
|
|
19
|
+
|
|
18
20
|
## 1. Native module abstraction (Wrapper)
|
|
19
21
|
|
|
20
22
|
**Use when:** Using native modules like Maps, Biometrics, or specialized Media players.
|
package/skills/ti-game/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Grep, Glob, Edit, Write, Bash(node *)
|
|
|
8
8
|
|
|
9
9
|
`ti.game` is Michael Gangolf's OpenGL ES 2.0 sprite engine for Titanium. JavaScript describes the scene and reacts to discrete events; Android and iOS run rendering, animation, physics, collision, gestures, audio, particles, ropes, tile layers, camera work, and telemetry natively.
|
|
10
10
|
|
|
11
|
-
This skill is verified against upstream `main` at **`
|
|
11
|
+
This skill is verified against upstream `main` at **`3bea2f4` (2026-09-02)**. Both manifests say **`0.6.0`**, which is unreleased; the published **`0.5.0`** release carries the same engine code, because its tag sits on the commit immediately before the version bump. Treat the commit or build date as the capability marker, never the version string — a locally built artifact stamped `0.5.0` can predate that release by weeks. Feature-detect an installed build when the exact artifact is unknown.
|
|
12
12
|
|
|
13
13
|
The skill applies to **Classic and Alloy** projects. Nothing here requires PurgeTSS.
|
|
14
14
|
|
|
@@ -56,6 +56,9 @@ Do not move sprites from `setInterval`, `requestAnimationFrame`, or a JS loop. P
|
|
|
56
56
|
| Chain, tether, cape | `Game.createRope()` |
|
|
57
57
|
| Pause or slow the scene while still drawing | `gameView.timeScale` |
|
|
58
58
|
| Measure FPS, frame time, and draw calls | `debug: { hud: true }` or the `performance` event |
|
|
59
|
+
| Read a game controller | GameView `buttondown`/`buttonup`, `stick`, `trigger` (names normalized across pads) |
|
|
60
|
+
| Wrap the world around horizontally | `gameView.worldWrapX` + per-sprite `wrapWorldX` |
|
|
61
|
+
| React to the force of a hit | `solidimpact` with a per-sprite `impactThreshold` |
|
|
59
62
|
|
|
60
63
|
## Minimal scene
|
|
61
64
|
|
|
@@ -124,7 +127,7 @@ When sources disagree, use this order:
|
|
|
124
127
|
3. Upstream `README.md` and `TUTORIAL.md` for intended public usage.
|
|
125
128
|
4. Upstream `TODO.md` for what remains partial or absent.
|
|
126
129
|
|
|
127
|
-
|
|
130
|
+
The prose drift recorded at the previous pin is gone: upstream now states 34 demos (correct), names the HUD corners correctly, and documents `legend`, `firstGid`, `cols` and `rows` as creation-time on Android. What remains uncovered by any demo is `wallSlideSpeed` and the `onWallLeft`/`onWallRight` flags — `wallhit` itself is now exercised by `worldwrap.js` — so that part of the contract still comes from the README and both native implementations.
|
|
128
131
|
|
|
129
132
|
## Before returning code
|
|
130
133
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ti.game API reference
|
|
2
2
|
|
|
3
|
-
Complete JS surface of `ti.game` as of upstream `main` at `
|
|
3
|
+
Complete JS surface of `ti.game` as of upstream `main` at `3bea2f4`, 2026-09-02, verified against every Android proxy/engine and its iOS twin. Defaults come from native fields, not prose. The manifests read **`0.6.0`**, which is unreleased: the published **`0.5.0`** release carries the same engine code, because its tag sits on the commit right before the version bump. Read the number as a floor, never as a feature list — a locally built artifact stamped `0.5.0` may predate that release by weeks. Older `0.3.0`/`0.4.0` artifacts also vary by build date; feature-detect when the artifact is unknown. Read a property before writing it, because Kroll can retain an unknown write on the proxy. Methods are safer probes (`typeof Game.createTileLayer === 'function'`), as are normalized properties (`solidMode` reads an unknown name back as `'block'` only when the engine implements it).
|
|
4
4
|
|
|
5
5
|
Properties are live unless their table says creation-time or read-only. Every writable property can be passed to its `create*` factory. `TileLayer` has four cross-platform creation-time inputs (`legend`, `firstGid`, `cols`, `rows`) because Android does not expose the live setters that iOS has. All durations crossing the JS boundary are **milliseconds**.
|
|
6
6
|
|
|
@@ -10,6 +10,7 @@ Properties are live unless their table says creation-time or read-only. Every wr
|
|
|
10
10
|
- [Module](#module)
|
|
11
11
|
- [Names and percentages](#names-and-percentages)
|
|
12
12
|
- [GameView](#gameview)
|
|
13
|
+
- [Gamepads](#gamepads)
|
|
13
14
|
- [SpriteSheet](#spritesheet)
|
|
14
15
|
- [Sprite](#sprite)
|
|
15
16
|
- [Font](#font)
|
|
@@ -106,6 +107,7 @@ A normal Titanium view — add it to a window or any container, size it with the
|
|
|
106
107
|
| `cameraX` / `cameraY` | float | `0` | World-space offset of the view |
|
|
107
108
|
| `cameraScale` | float | `1` | Zoom, anchored on the view center |
|
|
108
109
|
| `cameraBounds` | dict / null | `null` | `{ minX, minY, maxX, maxY }` world rect the visible area is clamped into |
|
|
110
|
+
| `worldWrapX` | dict / null | `null` | `{ minX, maxX }` turns on a finite circular horizontal world; sprites opt in with `wrapWorldX`. Invalid or non-positive intervals disable it. **Horizontal `cameraBounds` are ignored while it is active**; the vertical limits still apply |
|
|
109
111
|
| `follow(sprite, options)` | method | — | Native dead-zone follow, see below |
|
|
110
112
|
| `stopFollow()` | method | — | Stop following; the camera stays where it is |
|
|
111
113
|
| `shake({ strength, duration })` | method | `12` px, `400` ms | Detuned-sine rumble on the projection only — follow, bounds and touch mapping are unaffected |
|
|
@@ -117,12 +119,18 @@ A normal Titanium view — add it to a window or any container, size it with the
|
|
|
117
119
|
| `cameraEffect` | string | `'none'` | Fullscreen shader pass: `'none'`, `'tint'`, `'glitch'`. With `'none'` the extra pass is skipped entirely |
|
|
118
120
|
| `cameraTint` | string | — | Color for the `'tint'` effect, e.g. `'#4f8'` (night vision, poison, flashback) |
|
|
119
121
|
| `cameraEffectIntensity` | float | `1` | 0..1 — tint mix or glitch amount |
|
|
122
|
+
| `gamepads` | array | `[]` | Read-only. Connected controllers as `[{ id, name }]` |
|
|
123
|
+
| `gamepad` | dict / null | `null` | Read-only snapshot of the most recently used pad: `{ id, name, leftX, leftY, rightX, rightY, l2, r2, buttons }` |
|
|
124
|
+
| `gamepadDeadzone` | float | `0.2` | Radial stick dead zone with rescaling, clamped to 0..0.9, so small values start at 0 instead of jumping |
|
|
125
|
+
| `gamepadStickPress` / `gamepadStickRelease` | float | `0.5` / `0.4` | Left-stick thresholds for reporting a direction as a button. Press clamps to 0.1..0.95; release stays between 0.05 and press. The gap is hysteresis against flicker |
|
|
120
126
|
| `debug` | bool / dict | `false` | `true` draws all collision shapes. Object form: `{ hitbox, hud, hudFont }`; `hud` is `true` or a corner name. Readback is normalized to `{ hitbox, hud: false | corner }`. See [debugging-performance.md](debugging-performance.md) |
|
|
121
127
|
|
|
122
128
|
Events: `press`, `tap`, `release` — fired for **every** touch anywhere on the view, with payload `x`, `y` in scene coordinates (tap-anywhere controls, flappy-style). `resize` carries the real surface `width`, `height` in pixels. `performance` carries a one-second telemetry snapshot only while a listener exists; see [debugging-performance.md](debugging-performance.md).
|
|
123
129
|
|
|
124
130
|
Plus `timer` with payload `id`, fired only for `after()` / `every()` calls made **without** a callback — pass a callback or listen for the event, not both.
|
|
125
131
|
|
|
132
|
+
Kroll never runs a `@Kroll.setProperty` setter for a key passed to `createGameView()`: each one needs its own branch in the native creation handler. `cameraX`, `cameraY`, `cameraScale` and `cameraBounds` were silently dropped at creation before `3bea2f4` and are honored from it on. Assigning them after creation always worked and still does — prefer that when the installed artifact is unknown.
|
|
133
|
+
|
|
126
134
|
### `follow(sprite, options)`
|
|
127
135
|
|
|
128
136
|
| Option | Default | Effect |
|
|
@@ -165,6 +173,25 @@ if (path) {
|
|
|
165
173
|
}
|
|
166
174
|
```
|
|
167
175
|
|
|
176
|
+
## Gamepads
|
|
177
|
+
|
|
178
|
+
A Bluetooth or USB controller (Xbox, PlayStation, Stadia, any MFi pad) reaches JS as discrete GameView events. There is no polling and no per-frame traffic; `gameView.gamepad` exists for the rare case where reading from a `gameView.every()` timer fits better than reacting to events.
|
|
179
|
+
|
|
180
|
+
| Event | Payload | Notes |
|
|
181
|
+
| --- | --- | --- |
|
|
182
|
+
| `buttondown` / `buttonup` | `button`, `input`, `gamepad`, `keyCode` | `button` is normalized across pads: `a` `b` `x` `y` `l1` `r1` `l2` `r2` `l3` `r3` `start` `select` `home` `up` `down` `left` `right`. iOS reports `keyCode: 0` |
|
|
183
|
+
| `stick` | `stick` (`'left'`/`'right'`), `x`, `y`, `gamepad` | Analog, throttled to ~20 Hz per channel while the value changes |
|
|
184
|
+
| `trigger` | `trigger` (`'l2'`/`'r2'`), `value`, `gamepad` | Analog travel, same ~20 Hz throttle |
|
|
185
|
+
| `gamepadconnected` / `gamepaddisconnected` | `gamepad`, `name` | A pad already paired when the view was created announces itself on its first input, so a connected event always precedes that pad's buttons |
|
|
186
|
+
|
|
187
|
+
The four directions arrive **both** from the d-pad (however the pad reports it, keys or hat) and from the left stick once it crosses `gamepadStickPress`, releasing below `gamepadStickRelease`. Each name is reported down once, however many physical controls map to it, so one `buttondown` table covers both without asking which the player used; `input` (`'dpad'`, `'leftstick'`, `'button'`, `'trigger'`) tells them apart when that matters. Analog triggers count as `l2`/`r2` buttons past half travel *and* fire `trigger` events.
|
|
188
|
+
|
|
189
|
+
Transitions skip the throttle: leaving rest, returning to rest and crossing zero are reported the moment they happen, and a channel always ends on its rest value — a released stick reports `0, 0` rather than leaving the hero drifting. `y` follows the engine's y-down convention, so `-1` is up. Everything held is released (a `buttonup`, plus a final rest-value `stick`/`trigger`) when the app backgrounds or the pad disconnects, so a direction never sticks.
|
|
190
|
+
|
|
191
|
+
On Android the buttons are captured at the activity window: the game view needs no focus, and mapped buttons are consumed so `b` does not act as Back and the d-pad does not move focus between overlay buttons. Unmapped keys (Back, volume) pass through. On Android 11+ the module opts the window out of joystick motion batching, so stick changes arrive as the pad reports them instead of once per display frame.
|
|
192
|
+
|
|
193
|
+
`platformer.js` runs on either the on-screen buttons or a pad.
|
|
194
|
+
|
|
168
195
|
## SpriteSheet
|
|
169
196
|
|
|
170
197
|
Create one sheet per texture and share it across every sprite that uses it — sprites sharing a sheet batch into a single draw call.
|
|
@@ -231,6 +258,10 @@ Methods: `play(name)` starts an animation, `stop()` halts it on the current fram
|
|
|
231
258
|
| `rotatable` | `false` | Two-finger rotate |
|
|
232
259
|
| `touchEnabled` | `true` | `false` = touches pass through to sprites underneath |
|
|
233
260
|
|
|
261
|
+
`drag`, `pinch` and `rotate` all cross the bridge at ~10 Hz, and each one always delivers its final value when the gesture ends, so the value a handler last saw is the value the sprite kept. Before `9c45cf6` only `drag` was throttled and the other two fired per motion event. The two-finger rotation delta also takes the short way round the +/-180 degree flip from that commit on; earlier builds could add a whole turn in one move.
|
|
262
|
+
|
|
263
|
+
The touch slop that separates a tap from a drag is a screen-pixel distance divided by `cameraScale`, so a tap stays a tap at any zoom. `screenFixed` sprites keep the raw pixel slop, since they already live in screen space. Earlier builds compared a pixel constant against world units, which made taps too forgiving when zoomed out and too strict when zoomed in.
|
|
264
|
+
|
|
234
265
|
Hit-testing runs against the transformed shape (rotation and scale included), topmost first, and is multi-touch: each finger runs its own gesture and a sprite belongs to at most one finger. A second finger landing on empty space — or on the sprite already held — pinches/rotates that sprite instead, per its flags.
|
|
235
266
|
|
|
236
267
|
### Physics
|
|
@@ -245,6 +276,7 @@ Hit-testing runs against the transformed shape (rotation and scale included), to
|
|
|
245
276
|
| `angularVelocity` | `0` | deg/s |
|
|
246
277
|
| `thrust` | `0` | px/s² along the current heading (Newtonian flight) |
|
|
247
278
|
| `wrapAround` | `false` | Re-enter from the opposite screen edge (Asteroids) |
|
|
279
|
+
| `wrapWorldX` | `false` | Opt this sprite into the GameView's circular `worldWrapX` interval. Native movement normalizes `x` inside it; rendering, touch, sprite overlap, solid resolution and swept movement all choose the nearest periodic image when **both** sprites participate. Takes precedence over `wrapAround`, `wrapX` and `wrapShift`; `screenFixed` sprites are excluded. A `TileLayer` repeats across the seam only when its `x` equals `minX` and its width equals `maxX - minX`. `raycast()`, `findPath()`, particles, ropes and skid trails do **not** repeat periodically |
|
|
248
280
|
| `wrapX`, `wrapShift` | `0` | Scroll looping: at `x < wrapX`, add `wrapShift`. Two screen-wide copies with `{ wrapX: -W/2, wrapShift: 2*W }` and a negative `velocityX` make a seamless parallax layer with no JS in the loop |
|
|
249
281
|
|
|
250
282
|
### Solids
|
|
@@ -257,6 +289,7 @@ Hit-testing runs against the transformed shape (rotation and scale included), to
|
|
|
257
289
|
| `wallSlideSpeed` | `0` | Maximum downward velocity in px/s while either wall flag is true. `0` disables the cap. Upward velocity and slower falls are unchanged |
|
|
258
290
|
| `restitution` | `0` | 0..1 bounce factor. Read off **both** sides of the contact and mixed as `max(mover, solid)`, the way Box2D does it — so a springy floor bounces riders that are not themselves bouncy. Solids default to 0, so a scene that never sets it on a surface behaves exactly as it did before this existed. A bounce whose closing speed would come out under **40 px/s** is damped to a dead stop instead, so a settling body grounds rather than buzzing |
|
|
259
291
|
| `solidMode` | `'block'` | On the **solid**. `'block'` is an immovable wall (what every solid was). `'contain'` is an inward circular boundary — matched circles are kept *inside* its circumference (drums, bowls, lottery cages). `'push'` makes it a body in its own right: a matched circle and this one split the separation and exchange the closing velocity at equal mass. `'contain'` and `'push'` are **circle-on-circle only**; anything else, and any unknown string, falls back to `'block'`. Since 2026-08-27 |
|
|
292
|
+
| `impactThreshold` | `40` | Minimum compensated relative normal speed in px/s before this receiver fires `solidimpact`. Minimum 0. Each participant applies its own threshold and owns its own gate, which rearms only after more than 100 ms of confirmed separation in game time |
|
|
260
293
|
| `oneWay` | `false` | On the **solid**: pass-through except for landings on its top edge — classic platformer floors. Honoured by `'block'` solids only |
|
|
261
294
|
| `carryRiders` | `true` | On the **solid**: a moving solid carries whoever stands on it (velocity, tweens, idle wobble — wrap teleports excluded). Set `false` for world-scroll terrain that moves while the player is meant to stay put |
|
|
262
295
|
|
|
@@ -284,7 +317,7 @@ Two consequences that cost an afternoon each if you meet them by surprise:
|
|
|
284
317
|
| Property | Default | Notes |
|
|
285
318
|
| --- | --- | --- |
|
|
286
319
|
| `collisionGroup` | — | The tag *this* sprite carries |
|
|
287
|
-
| `collidesWith` | — | Array of groups this sprite reports overlaps with |
|
|
320
|
+
| `collidesWith` | — | Array of groups this sprite reports overlaps with. Since `9c45cf6` a single group name is accepted as shorthand for a one-element array, on this property and on `solidWith`; `null` clears either. Earlier builds required the array and threw on a bare string |
|
|
288
321
|
| `hitboxScale` | `1` | Shrinks the collision box **around the anchor** — which is what makes `anchor` the other half of the tuning: `anchor: 'bottom'` with `hitboxScaleY: '55%'` covers the lower half without lifting the box off the floor. Also takes `'80%'` |
|
|
289
322
|
| `hitboxScaleX`, `hitboxScaleY` | `1` | Per-axis corrections **multiplied on top of** `hitboxScale`, for art that fills its frame by a different fraction on each axis. A 20×44 drawing in a 32×48 frame needs `0.62` wide and `0.92` tall — no single `hitboxScale` describes it. Ignored by circle hitboxes. Since 2026-08-23 |
|
|
290
323
|
| `hitboxShape` | `'rect'` | `'circle'` — radius = half the smaller drawn side × `hitboxScale` (the per-axis scales are skipped: a circle has no axes). Circles resolve against solids along the contact normal (corner bounces) and get a round touch area. `'rotatedRect'` keeps the collision rect **turned with the sprite** (an OBB) instead of re-boxing it square to the screen: a plain `'rect'` at 45° is re-boxed around its turned corners and comes out 41% wider with a flat top that is not there. Only matters once `rotation` is non-zero, and it does honour `hitboxScaleX`/`hitboxScaleY`, unlike a circle. Covers solids, `collidesWith` overlap, `raycast`, the swept pass and the debug overlay. Since 2026-08-27 |
|
|
@@ -393,6 +426,7 @@ The rest of the contract, all read from the engine:
|
|
|
393
426
|
| `collision` | `group`, `other`, `x`, `y` | Overlap with a `collidesWith` group began |
|
|
394
427
|
| `collisionend` | `group`, `other`, `x`, `y` | That overlap ended: the shapes separated, **or** the partner was removed from the scene, hidden (`visible = false`) or stopped matching the group filter |
|
|
395
428
|
| `land` | `x`, `y`, optional `other`, optional `group` | Landed on top of a `solidWith` solid. `other` and `group` identify the solid sprite when one exists; TileLayer cells have no proxy and omit both |
|
|
429
|
+
| `solidimpact` | `group`, `other`, `x`, `y`, `contactX`, `contactY`, `normalX`, `normalY`, `speed`, `restitution` | A sprite-to-sprite `solidWith` response met this receiver's `impactThreshold`. Both sprites in the pair get the event, sharing the contact point and `speed` and carrying **opposite** normals; `x`/`y` is the receiver's own position. `speed` is the relative closing normal speed *before* restitution, compensated for the gravity, thrust, car model and damping applied that frame, and `restitution` is the effective mixed value. `other` can be `null` if its proxy was released before delivery. **TileLayer cells never emit it.** Contact points are exact for circles and face contacts; an OBB corner is a support-point approximation. Spin, surface velocity, tweens and `followPath` add no physical normal velocity, so they never generate it on their own. With no listener on either sprite the resolver returns before computing any of this, so an unused event costs nothing |
|
|
396
430
|
| `wallhit` | `side`, `x`, `y`, optional `other`, optional `group` | Began pressing a matching wall or switched sides. `side` is `'left'` or `'right'`. TileLayer cells omit both `other` and `group`; there is no `wallend` event, so read the flags in the jump handler |
|
|
397
431
|
|
|
398
432
|
## Font
|
|
@@ -571,8 +605,8 @@ Movers list the layer's `collisionGroup` in `solidWith`. Rect, circle, swept, re
|
|
|
571
605
|
|
|
572
606
|
Only these events exist. Nothing fires per frame, and events are only fired if a listener is registered.
|
|
573
607
|
|
|
574
|
-
- **GameView**: `press`, `tap`, `release`, `resize`, `timer`, `performance`
|
|
575
|
-
- **Sprite** (and therefore **Text**): `press`, `release`, `tap`, `dragstart`, `drag`, `dragend`, `pinch`, `rotate`, `animationcomplete`, `complete`, `pathcomplete`, `collision`, `collisionend`, `land`, `wallhit`
|
|
608
|
+
- **GameView**: `press`, `tap`, `release`, `resize`, `timer`, `performance`, `buttondown`, `buttonup`, `stick`, `trigger`, `gamepadconnected`, `gamepaddisconnected`
|
|
609
|
+
- **Sprite** (and therefore **Text**): `press`, `release`, `tap`, `dragstart`, `drag`, `dragend`, `pinch`, `rotate`, `animationcomplete`, `complete`, `pathcomplete`, `collision`, `collisionend`, `land`, `wallhit`, `solidimpact`
|
|
576
610
|
- **SpriteSheet, Font, Sound, Emitter, Rope, TileLayer**: none
|
|
577
611
|
|
|
578
612
|
There is `collision` (enter) and `collisionend` (exit), but deliberately **no** per-frame "stay" event — that would be bridge traffic every frame. Hold the in-between state in JS: you heard the enter, you will hear the end.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Debugging and performance telemetry
|
|
2
2
|
|
|
3
|
-
Verified against `ti.game@
|
|
3
|
+
Verified against `ti.game@3bea2f4`: both `GameViewProxy` implementations, `DebugHud`, `FrameStats`, both scene renderers, `example/camera.js`, `example/particles.js`, and `example/tilemap.js`.
|
|
4
4
|
|
|
5
5
|
## The two debug aids
|
|
6
6
|
|