tmt-game-generator · v1.0.0 · 2026-10-24 · sha256 538bd1470f0c5610
tmt-game-generator v1.0.0B
Immutable. This exact content never changes and is served at /api/v1/blob/538bd1470f0c5610.
---
name: tmt-game-generator
description: 通过对话生成完整、可游玩的 The Modding Tree (TMT) v2.7 增量放置游戏:采访定参 → 数据驱动的设计文档 → 内置引擎模板脚手架 → 生成 layers 代码 → 72 条静态规则加加载期冒烟 → 无头模拟器实测节奏 → 交付可测版本。适用场景:从零制作增量/放置/点击/声望树游戏;用户点名 The Modding Tree 或 TMT;需要开箱可玩、数值平衡、控制台零报错的成品。触发词:做增量游戏、制作放置游戏、生成点击游戏、idle game、incremental game、prestige tree、modding tree、The Modding Tree、TMT 游戏、帮我做个挂机游戏、声望树游戏、升级树游戏。不用于:给已有 TMT 游戏调数值或排错;其他框架(Profectus、ExpantaNum 系)的增量游戏。
description_zh: 通过对话生成完整、可游玩的 The Modding Tree (TMT) v2.7 增量放置游戏:采访定参 → 数据驱动的设计文档 → 内置引擎模板脚手架 → 生成 layers 代码 → 72 条静态规则加加载期冒烟 → 无头模拟器实测节奏 → 交付可测版本。适用场景:从零制作增量/放置/点击/声望树游戏,或用户点名 The Modding Tree / TMT。不用于给已有 TMT 游戏调数值、排错,以及其他框架的增量游戏。
description_en: Generate a complete, playable The Modding Tree (TMT) v2.7 incremental game from a short conversation: interview the player, write a data-driven design brief, scaffold from the bundled engine template, generate layer code, run 72 static checks plus a load-time harness, measure the pacing in a headless simulator, and hand off a tested build. Use for incremental, idle, clicker, prestige-tree and modding-tree games built from scratch, or when the user names The Modding Tree / TMT. Do not use to rebalance or debug an existing TMT game, or for incremental games on other frameworks (Profectus, ExpantaNum-based engines).
version: 1.0.0
author: TMT GG
---
# TMT Game Generator
Generate a complete, playable **The Modding Tree v2.7** incremental game: the player opens
`index.html` and gets a full prestige tree with balanced numbers, automation milestones,
achievements, and a reachable endgame — no console errors, working saves.
This skill is self-contained. Everything it needs is in this folder:
| Path | Role |
|---|---|
| `references/core/00–08` | Engine + coding handbook copies (00 overview, 01 Decimal rules, 02 mod.js, 03 layers API, 04 components, 05 UI, 06 idioms, 07 pitfalls/checklist, 08 real-game catalog) |
| `references/design/09-Layer-Design-Patterns.md` | Design patterns P1–P17, blueprints, measured heuristics, anti-patterns A1–A10 |
| `references/design/10-Fun-Mechanics-and-Content-Organization.md` | The fun pass: effect-kind taxonomy K1–K8, microtabs organization standard, mechanic catalog M1–M10, per-blueprint fun quotas |
| `references/design/11-Corpus-Numeric-Design.md` | **The wiring pass**: measured numeric baselines over all 329 real games — the 54.4%-no-effect finding, cost-ladder shapes, gain-curve ratios, softcap forms, and the ghost-field table |
| `references/design/12-Author-Design-Wisdom.md` | **The author's own design notes** (Acamaeda, 2020–2023): pacing, upgrade/buyable discipline, cost-vs-softcap, gates, readability. §0 lists the four places this skill previously taught the opposite |
| `references/design/13-Types.md` | Game-type judgment aid (Q4b): the four interaction models, modifiers + hard dependencies, routing |
| `references/design/14-Measurement-and-Harness.md` | **Stage 6 as a measurement**: the load-time smoke harness, the pacing simulator, calibration doctrine, and what only a simulation can find |
| `references/design/15-Text-Layer-and-Localisation.md` | The text layer beyond a two-way ternary: `js/lang/` convention, key namespacing, the seven integrity defects, T1–T4 tiers |
| `assets/blueprints.json` | Machine-readable progress blueprints (from 09 §3) |
| `assets/balance-defaults.json` | Machine-readable measured balance defaults (from 09 §4) + `corpusBaselines` (from 11) |
| `assets/fun-quota.json` | Machine-readable fun-density quotas per blueprint (from 10 §5) |
| `assets/design-brief-template.md` | The design document you fill BEFORE coding |
| `scripts/scaffold.js` | Creates the game folder from the TMT template |
| `scripts/static_checks.js` | Post-generation static checks (FAIL/WARN/PASS report) |
| `scripts/i18n_check.js` | Text-layer integrity (only when the game has `js/lang/`; exits 0 silently otherwise) |
| `assets/harness/smoke.js` | **Load-time harness** — copy to `<game>/tools/`, run before the browser (Stage 5.5) |
| `assets/harness/simulate.js` | **Pacing simulator** — copy to `<game>/tools/`, run before rebalancing (Stage 6) |
| `template/` | Bundled TMT v2.7 engine template (verbatim minus demo/docs — upstream MIT licenses included) |
The engine template ships **bundled** at `template/`, so the skill works from any install
location. `scaffold.js` resolves it as: `--from <dir>` > `TMT_TEMPLATE` env var > bundled
`template/` > sibling `The-Modding-Tree-master` folder > cwd.
---
## Pipeline (run all stages in order — never skip ahead)
| Stage | Output | Gate to next stage |
|---|---|---|
| 1. Interview | answers mapped to generation parameters | all 7 questions answered **by the user** + a game type **the user confirmed** (Q4b) |
| 2. Design brief & self-check | filled `design-brief` doc | every self-check passes (21 checks) |
| 3. Scaffold | game folder + recorded `modInfo.id` | scaffold exits 0 |
| 4. Generate code | js/mod.js, layer files, tree.js, theming | all files written & registered; **every upgrade classified token-vs-value and, if it has an effect, wired to a consumer** |
| 5. Static checks | check report (type profile applied) | 0 FAIL (WARNs justified in brief) |
| 5.5 Load-time smoke | harness report (`tools/smoke.js`) | 0 FAIL — every mod file loads, no duplicate ids, no fresh-save crash |
| 6. Balance walkthrough | annotated numbers, **measured in-sim** | heuristic table verified **and** the sim's early timeline matches the design intent |
| 6.5 Browser verification | boot + interaction report, fixes recorded in brief §14 | `__bootErrors` empty, every layer tab opens and renders, one real prestige per layer type, console clean |
| 7. Manual test handoff | test instructions for the user | 07 Part D checklist handed over |
**Load references on demand, not all at once** (keeps context small):
| When | Read |
|---|---|
| Stage 1, after Q4 | `references/design/13` (§1 types + §3 routing); run `scripts/classify.js` |
| Stage 2 | `references/design/09` (fully), `references/design/10` (fully), `references/design/11` (fully), `references/design/12` (fully), all `assets/*.json`, brief template |
| Stage 4, any code | `references/core/01` (Decimal rules — mandatory before ANY code), then `02` for mod.js, `03` + `04` for layers, `06` §3 wiring map + §7 per-layer directives |
| Stage 4, design rules | wiring discipline `references/design/11` §2; gates, costs, readability `12`; fun density `10` |
| Stage 4, language scope | `references/design/15` + `scripts/i18n_check.js` (T2 and above only) |
| Stage 4, theming/UI | `references/core/05` |
| Stage 5.5–6 | `references/design/14` (smoke harness, simulator, calibration doctrine) |
| Stage 6.5 | `references/design/14` §5 (the browser-verification checklist and why each line exists) |
| Stage 5–7 | `references/core/07` (hard rules + Part C crash table + **Part F engine facts** + Part D checklist) |
| Design inspiration | `references/core/08` (real-game catalog) |
---
## Stage 1 — Interview
Goal: ≤7 structured questions, each locking concrete generation parameters. Ask via your
tool's question UI (2–4 options + free-text "Other") or plain conversation. Output the
question text **in the user's language**; record answers in English in the brief.
**Every answer comes from the user — there is no auto-answer mode.** The AI never fills in,
guesses, or defaults a question on the user's behalf. Anything the AI derives (a theme
proposal, a routed type, a modifier pick) is *shown* to the user and locked only on their
explicit confirmation, recorded next to the answer in the brief.
**Step 0 (only if the user gave no theme):** ask what the game is about (free text), then do Q1.
If the user said "you pick the theme", propose 2–3 candidates **and wait for their pick** —
the theme is still a user answer, never an AI decision.
**Q1 — Theme-structure interview (confirmation).** Before asking, restate your structured
understanding of the theme in exactly three lines:
- **Currencies** — what the player accumulates, in order (e.g. "study hours → subject scores → exam rank")
- **Layer topology** — which layers exist, which reset which (e.g. "4 subject layers → one exam hub")
- **Ceilings** — any natural maximum per currency (e.g. "each subject score caps at 150")
Options: `Confirmed as described` / `Adjust (explain in Other)`. There is no
"let the AI decide" option — if the user asks for a structure proposal, draft one, show it,
and come back to this same question.
This is the theme-is-structure question (P14): the theme must dictate the tree, not just names.
**Q2 — Scale (locks the blueprint).**
- `Small — 5–7 layers, ~2–4h` → blueprint `small`
- `Medium — 10–16 layers, ~10–30h` → blueprint `medium`
- `Large — 25+ layers, 50h+` → blueprint `large`
- `Custom — the theme cannot be expressed as a layer chain at all` → see below
The answer locks ALL structure parameters from `assets/blueprints.json` (rows, per-layer
component counts, automation ladder, endgame). Do not improvise beyond it **except** through
the custom path, which has its own contract:
**Custom blueprint contract.** Two real requests legitimately needed it: *"one layer per color —
16,777,216 layers, made feasible"* (a medium tree carrying a colour-cube completion space) and
*"one single layer, enormous content, 100+ hours"* (a `monolith`). Both are honest designs that
`small`/`medium`/`large` cannot describe. If you take this path: (1) **ask the user to confirm the
substitution** — never silently reinterpret the request; (2) **name the stock blueprint whose quotas
you are bending** and draw the fun quota from it (the monolith used the `large` row); (3) write
`blueprint: "custom"` in `.tmt-profile.json` and record the substitution in brief §10; (4) **state
which self-checks become N/A** (a single layer has no requires ladder, no row pacing, no cross-layer
gainMult web) and which are **exceeded by design** — a check that is N/A is written down as N/A,
never quietly passed; (5) do not let the custom blueprint void the type axis — Q4b still applies.
**Q3 — Natural ceilings.** Does the theme have built-in upper limits (exam score 150,
percentages, levels, distance-to-zero)?
- `Yes, hard ceilings` → capped currencies use `type: "custom"` + `softcap(x, cap, 0)` hard
caps; their ladders STOP at the cap (P8a Gaokao pattern)
- `No, unbounded` → standard normal/static chain + P12 triple softcaps
- `Mixed` → per-currency decision in the brief's cap table
**Q4 — Pacing & interactivity.**
- `Mostly idle/AFK-friendly` → logarithmic/custom gains allowed (P8b Incrementreeverse
pattern) + longer automation ladder (8 steps); mechanics skew to M2 score-attack challenges
and M5 secondary currencies (progress continues while away)
- `Mostly active` → standard sqrt chains; include an interactive mechanic (M3 risk-reward
minigame OR M4 bar+clickable active production — handbook 10 §4); at most ONE `type: "none"`
core-loop layer built from clickables/bars/update() (P15 — use sparingly, one max)
- `Balanced` → default blueprint behavior + the blueprint's fun-quota mechanics
(`assets/fun-quota.json`) — **and Q4b will ask what the player actually does.** "Balanced"
is not permission to install every mechanic in the quota list; the generated game picks its
mechanics from the locked type plus modifiers.
**Q4b — Game type (interaction model).** Q2 locked the game's *scale*; Q4b locks its *shape*.
The two axes are independent — do not merge them, and do not change the blueprint.
Derive the type from what the player **does**, never from the theme. Run:
```bash
node scripts/classify.js --request "<the user's request verbatim>" \
--recap "<the Q1 three-line recap>" \
--q4 <idle|active|balanced> [--json]
```
Exit 0 = the script proposes a route — **show the user the derived type with its confidence
and the ranking behind it, and lock the type only on their confirmation.** The auto route is
a proposal, never an answer. Exit 3 = the script could not decide and returns a question —
**ask the user that question verbatim, in their language, then re-run with `--explicit
<typeId>`.** Never guess past an exit-3, and never let a keyword override an explicit request.
| Type | The player… | Layers | Exempt |
|---|---|---|---|
| `passive-prestige` | accumulates / unlocks / resets, never acts | `normal`, `static` | `D-NOUPDATE` |
| `active-click` | taps, chops, presses — a discrete action | + `clickables` | — |
| `active-tick` | pushes a continuous quantity that fills over time | + `bars` + `update()` | — |
| `board-minigame` | plays on a 2D board | + `grid` | — |
Then propose modifiers, checking each hard dependency — both the picks and the drops go in
front of the user together with the type confirmation (drops are logged in brief §10):
| Modifier | Requires | Drop it (and log why in brief §10) if… |
|---|---|---|
| `sim` | — | more than 3 layers would use it |
| `score-attack` (M2) | type `active-*` **and** `sim` | the game has no tick — M2 records a peak inside `update()`, and the engine has **no** score-attack API |
| `minigame` (M3) | type `active-*` | there are no clickables to click |
| `board`, `challenges`, `caps` | — | never |
**At most one interaction type.** Everything else is a modifier. If the user wants all of
them, ask which one is the core and treat the rest as modifiers.
**The one sanctioned override.** If the *user's own words* put different interaction models in
different parts of the game ("arbitrary gameplay", "early clicking, then board games, then pure
idle"), that is a design the type axis cannot express rather than a routing failure — one real
build ran four eras (tick → click → board → passive) in a single layer. Only when the user asked
for it, and then: declare the **primary** type, demote the rest to modifiers with their
dependencies re-checked, **forfeit every exemption** (the tick must be real), keep all `T-*` rules
active, and log the override as a numbered deviation in brief §10.
Record in brief §2b: the type, the user's confirmation, the confidence, and any modifier you
dropped and why.
Full judgment aid: `references/design/13`.
**Q5 — Side content.**
- `Full — challenges (M2 score-attack hub for medium+) + achievements + story/dashboard side layer`
- `Standard — challenges (M1 classic) + achievements`
- `Light — achievements only`
**Q6 — Automation & timewall tolerance.**
- `Automate everything eventually; no long walls` → full 8-step ladder, walls < 15 min
- `Classic pacing` → 4–6-step ladder, walls up to 30 min mid-game
- `Speedrun-friendly` → short walls (< 5 min), earlier automation
**Q7 — Language scope & style.** Ask for the *scope*, not a language list — the tier decides how
much of Stage 4 changes (handbook 15):
- **T1 `Single language`** — write strings inline.
- **T2 `Two languages, one toggle`** — the `P17` convention: a `window.chinesemode` flag with
`cond ? "中文" : "English"` ternaries on user-facing strings.
- **T3 `Several complete languages, switchable`** — the `js/lang/` text layer: one
`t(key, fallback)` accessor, complete packs, a settings dropdown. Do **not** use ternaries here.
- **T4 `Mixed / polyglot`** — T3 plus a phrase pool assigning each sentence its own language (one
real build: 370 sentences, 370 languages).
- Optional style notes via Other: colour mood, naming voice (feeds per-layer `color`/`symbol`).
T2 and above add four mandatory items regardless of tier: lang files load **before `js/mod.js`**;
descriptions/titles/tooltips/`winText` are **functions**, never module-scope values; `main-display`
is **overridden** (it hard-codes `You have` / `(x/sec)` — 07 Part F7); `scripts/i18n_check.js`
reports 0 FAIL.
Record every answer; they are inputs to Stage 2 and must be traceable in the brief's §2 table.
---
## Stage 2 — Design brief & self-check
**Write the brief BEFORE any code.** Copy `assets/design-brief-template.md` (next to the
future game folder or in your notes) and fill every section:
1. Select the blueprint from `assets/blueprints.json` per Q2 and copy its row plan, side
layers, automation ladder, and endgame into the layer-chain table.
2. Pull numbers from `assets/balance-defaults.json`; every deviation must be written into
brief §10 with the default it overrides and why.
3. Complete the layer chain table — one row per layer, bottom row first, EVERY column
(id/name/row/position/type/base/requires/exponent/components/automation granted/softcap
plan/doReset keeps/branches). This table is the code-generation contract.
4. Fill the brief's **§4.1 fun-density table** (effect-kind plan + organization + mechanics per
layer) and **§4b game-level fun plan** against `assets/fun-quota.json` for the locked
blueprint: label every upgrade/milestone with an effect kind K1–K8 (handbook 10 §2), choose
the M1–M10 mechanics the theme supports, and plan each layer's tabFormat/microtabs sections
(handbook 10 §3).
**Self-check the brief** (fix the table, not the code, when any fails):
| # | Check | Source |
|---|---|---|
| 1 | requires ladder monotonic; adjacent rows ×10–×100 | 09 P2, §4; A1 |
| 2 | first layer requires 2–10 (mode 10) | 09 P1 |
| 3 | every main layer ≥10 upgrades + ≥3 milestones | 09 §5 A4 |
| 4 | every new layer boosts ≥2 older layers in gainMult; ≥1 upgrade boosts layer 1 | 09 P10; A7 |
| 5 | automation ladder 4–8 steps, P4 order (resetsNothing → passiveGeneration → autoPrestige → autoUpgrade), one chore per step, cross-layer grants | 09 P4; A8 |
| 6 | softcap plan for every unbounded loop; hard caps for theme ceilings | 09 P12 |
| 7 | anti-pattern sweep A1–A10: none present | 09 §5 |
| 8 | effect diversity: K1 flat-mult share within `fun-quota.json` maxK1SharePerLayer; ≥3 distinct non-K1 kinds per layer (K7 unlocks excluded from the K1 count) | 10 §2 |
| 9 | organization: tabFormat on every main layer; microtabs planned once a layer has ≥3 content groups; context display-texts (base, best, softcap warning) above tabs | 10 §3 |
| 10 | game-level fun quota met (mechanics M1–M10 count, challenge style, clickables/bars/update floors for the blueprint) | 10 §5; fun-quota.json |
| 11 | multiplier budget: **every `.times()` source is an independent multiplicative zone and zones compound** — at most ONE self-scaling upgrade per layer (reads own points, pow ≤0.5, softcapped); Σ self-exponents wired into any gainMult ≤ 0.6; product of all multiplier zones reachable when layer N+1 unlocks must stay within the requires ladder's assumed production (if end-of-row-0 sugar mult > ~1e4 the ladder is fiction) | 09 P12; static rule N-SELFTOTAL |
| 12 | **wiring plan**: every upgrade is classified in the brief as token/gate (K7, no `effect`) or value (has `effect` → the brief names its consumer, e.g. "wired into layer N `gainMult` via `hasUpgrade`"). No upgrade may sit in neither column. | 11 §2; N-UNWIRED |
| 13 | cost ladder per layer spans ≥6 orders of magnitude, with at least one deliberate drop marked as a key upgrade; ≥30% of a layer's upgrades use a `pow`/`log` shape | 11 §3–§4; D-COSTSPAN, D-EFFECTSHAPE |
| 14 | component density 8–30 per layer; challenges ≤15% of the layer's upgrade count; a layer-level `softcap` on every unbounded loop | 11 §1, §5 |
| 15 | **author pacing**: first boost reachable within 10 seconds; every reset longer than a minute pays something; a major reset speeds the game up from the first second; no hard resets ever | 12 §1; `authorPacingTargets` |
| 16 | **upgrade discipline**: one production bonus per upgrade, never 3+; no upgrade boosts another upgrade's effect (boost resources or named mechanics); layers are gated by threshold, never behind a purchase you then also pay for | 12 §3 |
| 17 | **cost before softcap**: shape with `cost()`/`requires`; use softcaps only for genuine runaway loops, and only sell a cap raise if the cap is visible | 12 §2; 09 P12 (revised) |
| 18 | **row pacing written as two constants**: `per-row time = requires step ÷ output step`, target **×1.1–×1.6**. Both numbers appear in the brief with the resulting multiplier beside them. No static rule can check this — it spans layers and generator constants | 11 §4.1 |
| 19 | **reset lifecycle**: every layer whose milestones read `best`/`total` keeps both in `doReset`; `autoPrestige` implies `resetsNothing` (compare milestone sets); no `update()` assigns `player[layer].points`; every static layer has `canBuyMax()` | 03 §4.1; N-MSDESTROY, N-AUTOWIPE, N-POINTSWRITE, N-STATICMAX |
| 20 | **type contract (Q4b)**: every module the locked type makes mandatory is planned and every forbidden one is absent; each modifier's hard dependency holds (M2 needs a tick, M3 needs clickables); every dropped modifier has a reason in §10 | 13 §1–§3; `assets/type-registry.json`; T-CLICKABLE, T-GRID |
| 21 | **measurement plan**: the brief names the pacing constants and where they live (`js/tuning.js`, read by the game *and* the simulator), and every quantity the pacing depends on is exposed as a callable function — a pacing that cannot be simulated cannot be verified | 14 §3.3; Stage 6 |
Also check no two milestones of the same layer claim the same automation grant (the Mario
Maker 2 "conflict point" incident). Gate: proceed only when all 21 checks pass.
**Write the brief so it can be refuted.** These checks verify *internal consistency*; Stage 5.5
and Stage 6 verify *the game*, and three real briefs that passed all 21 were then overturned by
measurement. Write the numbers so a later simulation can contradict them, and expect it to.
---
## Stage 3 — Scaffold
```bash
node scripts/scaffold.js "Game Name" [output-dir] [--author Name] [--points-name points] [--from /path/to/The-Modding-Tree-master] [--type <typeId>] [--modifier id,id]
```
The script copies the whole TMT template and patches `js/mod.js` (name, a unique
`modInfo.id` = slug + random suffix, author, pointsName, modFiles, VERSION 0.1, changelog
and winText placeholders; `offlineLimit` stays 1 hour). It verifies the patch and
syntax-checks the result.
`--type` / `--modifier` are **optional**. With them, the scaffolder writes
`.tmt-profile.json` into the game folder; Stage 5's checker reads it and applies that type's
gates and thresholds automatically. Omit them and nothing changes.
`--author` is never invented: if the user has not given a name, show them the scaffolder's
default (`author: "AI"`) and let them confirm or replace it — no parameter lands in the game
unseen.
- **Record `modInfo.id`** from the output — it keys the localStorage savefile and must never
change afterwards (07 rule 3).
- **README:** the scaffolder writes a **game-first skeleton** (play instructions, the save key,
placeholder content counts, a file table) and keeps the engine's own text below a
`## The Modding Tree (engine)` heading — the template's README, shipped as-is, is the
*engine's*, with two dead links and no word about the game. At Stage 3 fill its TODOs (the
one-line design from brief §1); at Stage 6 re-check the counted totals against the frozen
build (the README quotes numbers; they must match the gate output, not the plan). Count by
capturing `addLayer` calls and reading `Object.keys(def.upgrades)` per layer — brace-counting
the generated source over-counts (it once reported 200 upgrades / 30 achievements for 100/15).
- If template lookup fails, pass `--from <template-dir>` explicitly.
- If the output dir exists, choose another or pass `--force`.
**Scaffolding from a UI fork (`--from`).** A user may hand you their own TMT fork (one real build
did: a restyled "Nebula" fork). Before generating a single layer:
1. **Diff the engine-critical files** against the bundled template — `js/game.js`, `js/utils.js`,
`js/technical/layerSupport.js`, `js/technical/temp.js`, `js/utils/save.js`. The lifecycle rules
(`N-MSDESTROY`, `N-AUTOWIPE`, `N-POINTSWRITE`, `N-STATICMAX`) cite those files by line number,
so re-verify the lines you rely on or say in the brief that you did not. Only the UI files
(`components.js`, `canvas.js`, `displays.js`, `systemComponents.js`, `treeView.js`, `options.js`)
may differ freely.
2. **Re-apply the canonical patches the stock template already carries.** The one that bites:
`js/technical/displays.js:45` — a stock-based fork keeps `visibility: hidden` for uncompleted
achievements, which is exactly what `N-ACHVIS` (FAIL) exists for. Rules are not disabled
because the template is a fork.
3. **Record the fork in brief §10** with the engine-file verification result, then run Stage 5 and
5.5 against *your* output — both are template-agnostic.
On success you have a runnable (empty) game. Everything below upgrades it into the real one.
---
## Stage 4 — Generate code
Read `references/core/01` first — Decimal rules are absolute. Then generate in this order:
### 4.1 `js/mod.js` (references/core/02)
- `getPointGen()`: base gain `new Decimal(1)`/sec, then wire every promised boost (wiring
map: references/core/06 §3 — point boosts HERE, layer-gain boosts in `gainMult()`,
automation via milestone-gated `passiveGeneration`/`autoPrestige`/`autoUpgrade` flags).
- `isEndgame()`: the concrete Decimal target from brief §7 (not the placeholder).
- `initialStartPoints` small (10); `displayThings` status lines if useful; keep
`doNotCallTheseFunctionsEveryTick` and register any custom action-function you invent.
### 4.2 Layer files, bottom row first (references/core/03, 04; directives: 06 §7)
One `addLayer(id, {…})` per layer — **always an inline object literal** (N-FACTORY) — in files
listed in `modInfo.modFiles`, and every file must be appended to modFiles. Per the brief's table:
- `startData()` with `unlocked` + `points: new Decimal(0)` (+ `best`/`total` if milestones gate
on them — they should); `color`, `resource`, `symbol`, `row`, `position`.
- Prestige config per blueprint: `normal` (+`exponent` 0.5 → 1/3 → 0.25 by row), `static`
(+`base`, `roundUpCost`, `canBuyMax()`), or `custom` (P8: `getResetGain`/`getNextAt`/`canReset`,
multipliers placed AROUND `pow(exp)` so exponents do not amplify them). Always `baseResource` +
`baseAmount()` + `requires`.
- Content to the density floor: ≥10 upgrades on a single-digit-column grid, first ~cost 1, last an
exit ticket; ≥3 milestones chained by `unlocked()`, scoring on `.best`/`.total`; buyables with an
exponential cost; challenges/bars/clickables per Q5 and the locked type.
- The four budget rules that are hard FAILs: **N-UNWIRED** (every `effect()` id must be consumed),
**N-PGBOOL** (`passiveGeneration()` returns a plain number), **N-SELFTOTAL** (Σ self-scaling
exponents ≤ 0.6, and `ln(effect)/ln(cost) < 1` for buyables), **N-UNLOCKDEAD** (every
`layerShown` gate needs a writer).
- `gainMult()` = the P10 web: list every older layer's boost that reaches this layer, and make
this layer boost ≥2 older layers.
- `doReset(resettingLayer)` only when keeping things (P9); softcaps per P12.
### 4.3 Tree, hotkeys, achievements side layer
- `js/tree.js` usually needs no change (default tree from rows/positions); add ghost spacers
or `addNode` decorations only if the design wants them.
- Cap-raising upgrades (K5: "softcap starts N× later", tolerance raises) get **no** `effect()`
wired anywhere near gains — the cap function reads `hasUpgrade(...)` directly and the
upgrade's `effect()`, if any, only feeds its own `effectDisplay`. Wiring a cap value into
`getPointGen()` multiplies the base currency BY the cap (a real generated game: sugar ×1e12).
- Side achievements layer (`row: "side"`, P13): grid of real, checkable goals; rewards =
flat mults + ONE automation grant; any backfill `player.X.upgrades.push(id)` MUST be
guarded: `if (hasAchievement("a", 12) && !hasUpgrade("p", 13)) player.p.upgrades.push(13)` (P11).
**Achievements are always visible** — uncompleted ones show dimmed (`.locked` grey) with
name + tooltip, completed ones color-highlight (`.bought` green); the template's
`achievementStyle()` is patched to dim instead of `visibility: hidden` (static rule
N-ACHVIS fails if that engine default reappears). Never define `unlocked()` to hide goals.
### 4.4 Theme pass (P14/P17)
- Theme = structure (already fixed in Q1/brief): themed resource names, `symbol`s (2 chars),
one accent `color` per layer, `resetDescription`/`prestigeButtonText` in theme voice.
- Formula-on-Shift QoL: `["display-text", function() { if (shiftDown) return "…" }]`.
- Language scope per Q7: T2 = `window.chinesemode` ternaries; T3/T4 = the `js/lang/` text layer
(**references/design/15**). T2+ must override `main-display` — it hard-codes English chrome.
- Numbers: EVERYTHING through `new Decimal(...)` (strings like `"1e10"` for big values);
display via `format()`/`formatWhole()`; NO native operators on currencies; no `.mod()`;
no `resetBuyables()`; Vue2 syntax only. Full red lines: references/core/07 Part A/B.
### 4.5 Structures no tool can see through (07 Part F)
Each of these shipped as a defect in a real build, and each is invisible to all 72 rules:
- **Never build a layer at runtime.** `addLayer(id, factory())` hides `row`, `startData` and every
component from the checker and the harness (F11) — one build took 8 FAILs from this.
- **One home for pacing constants** (`js/tuning.js`), read by the game *and* the simulator: a
literal inlined in a layer file is a constant Stage 6 cannot tune (14 §3.3).
- **Single-digit columns.** Ids are object keys; `row*10 + col` collides at col ≥ 10 and the engine
drops entries without a word (F5).
- **Track lifetime totals by hand** — this engine has no `player.totalPoints` (F10).
- **Set `modInfo.allowSmall`** if any value can go below `1e-4`, or `format()` prints `0.00` (F1).
- **Price a discrete endgame purchase above the top of the ladder it ends** (F15); `layeradd` needs
a plain number and `winText` must be a function (F2/F3).
---
## Stage 5 — Static checks
```bash
node scripts/static_checks.js "<game-folder>"
```
- **FAIL blocks manual testing.** Fix the code — or, for a design-assertion failure, fix the
design — and re-run until 0 FAIL. Testing a build with open FAILs wastes the test.
- **WARN needs a written acceptance reason in brief §9.1** (the template's static-warning
acceptance table). An unexplained WARN counts as a
failure. Legitimate reasons do exist: a deliberate ×150 requires leap for a multi-row jump, a
hard-capped currency whose ladder cannot span three orders, a sink layer that must never reset.
- **The rule catalogue is the checker's own header, not this file.** `scripts/static_checks.js`
documents all 72 ids with their level, their provenance and the incident that produced each.
Read it there: a second copy here would drift, and had already (this section was 20 rules
behind). `--json` gives machine-readable output; the rules span `M-*` mod.js completeness,
`C-*` banned calls, `D-*` design assertions, `N-*` negative examples from real builds, and
`T-*` type contracts.
- **Type gating.** The type locked in Q4b decides which rules are enforced and which are exempt.
The checker reads `.tmt-profile.json` (written by `scaffold.js --type`), `--profile <typeId>`
overrides it, `--no-profile` disables it. With no type declared the report is byte-identical to
the pre-type checker.
- **Fixture-proof.** `tests/fixture-good` must stay clean, `tests/fixture-bad` must trigger every
injected defect with zero false positives, `tests/fixture-ghost` covers the ghost-field table.
Run `node tests/run_tests.js` after touching the checker or any fixture.
- **Text layer:** `node scripts/i18n_check.js <game-folder>` — 0 FAIL on missing keys, duplicate
language codes, duplicate endonyms and placeholder tokens; a silent no-op without `js/lang/`.
- **Count the GENERATED code, not the generator edits.** When a fix is applied inside a
generator/template rather than to the output, verify by counting occurrences in the finished
files. Learned the hard way: a postmortem fixed a defect in 31 of 34 affected layers because
the generator had *two* code paths producing them and only one was patched — the 3-layer miss
only surfaced in a browser. After any bulk edit, re-run `static_checks.js` on the real game
folder and compare counts against the number you intended to change; a rule that reports the
same number after a "complete" fix is telling you the fix did not land everywhere.
- **Run the text-layer check when there is one:** `node scripts/i18n_check.js <game-folder>` —
0 FAIL on missing keys, duplicate language codes, duplicate endonyms and placeholder tokens.
It exits 0 silently for single-language games, so it is safe to run unconditionally.
---
---
## Stage 5.5 — Load-time smoke (gate: before the browser, before Stage 6)
The static checker reads source text. It cannot tell you that a file throws while loading, that two
`addLayer()` calls share an id, or that a hook returns NaN against a fresh save — all three shipped
from this pipeline and were only found by opening the page.
```bash
cp <skill>/assets/harness/smoke.js <game>/tools/smoke.js # tools/ is never a modFile
node tools/smoke.js
```
- **0 FAIL is the gate.** Findings are named `S-LOAD`, `S-DUPID`, `S-STARTDATA`, `S-NAN`,
`S-UNDEF`, `S-HOOKTHROW`, `S-UPDATETHROW`, `S-PRESTIGE`, `S-FACTORY`.
- The harness starts **every layer unlocked** on purpose, so a hook that only breaks once the layer
is reachable is caught here instead of by a player.
- A finding that is really a *stub* gap is fixed in the script with a comment — never ignored. The
stub fidelity rules behind every phantom finding are in `references/design/14 §2`.
- Load order matters: the harness follows `index.html`, so a text layer loaded before `js/mod.js`
is loaded before it here too.
---
## Stage 6 — Balance walkthrough (a measurement, not an estimate)
**Read `references/design/14` before this stage.** Three real briefs passed all 21 checks and were
then overturned by play: one promised 8–18 h and delivered 2.5 h, one promised a sane multiplier
stack and detonated at 28 s, one promised six automation steps whose thresholds were unreachable.
Paper reachability is a consistency argument, not a prediction — run both passes and let the
measurement have the last word.
```bash
cp <skill>/assets/harness/simulate.js <game>/tools/simulate.js
node tools/simulate.js
```
Trust the **early timeline** (first affordable purchase, each layer's first prestige): it is
policy-independent and it is where the author rules bite. Do **not** quote the total until the
policy has been fitted to one known playthrough value — the script says so out loud when a run
finishes implausibly fast. Then complete the paper pass below and reconcile the two:
**where they disagree, the simulation wins and the brief is what changes.**
Go through brief §4's table against `assets/balance-defaults.json` and annotate each number
PASS/deviate-with-reason. Then do the reachability estimate (09 §4):
1. Estimate steady-state first-layer production `P` (points/sec × prestige gain multiplier
at the layer's unlock point).
2. Walk the ladder: each row's requires ÷ (production at that era, including the automation
steps granted by then) → rough minutes-per-era; sum them.
3. If the total misses the Q2 playtime target by more than ~10×, rebalance requires/cost
exponents in the brief FIRST, then regenerate numbers — do not tune code directly.
4. Confirm every runaway loop (effect feeding its own source) has a softcap, and the
endgame target is reachable but not instant (P12: first softcap ≈ next-layer unlock).
5. **Multiplier-zone walk** — per era, list every independent `.times()` zone active at
that moment (pointGen lines, gainMult lines, milestone/achievement mults, layer effects)
and MULTIPLY the guaranteed flat ones: if the product at end of row 0 exceeds ~1e4 (or
any layer's Σ self-scaling exponent ≥ 0.75), the requires ladder is fiction — cut zones
(merge flat mults into fewer, stronger ones), lower exponents, or tighten softcaps in
the brief. Zones compound: ten "harmless" ×2s are ×1024.
---
## Stage 6.5 — Browser verification (the agent opens the game, not just the files)
Static checks, smoke and the simulator all read the game without a real browser, and three real
builds shipped browser-only breakages because nobody had opened the page: a `body onload` boot
race (half-built `layers`), a `doReset` that wiped the currency it had just granted (the sim's
port auto-kept `points` and masked it), a render-time throw that aborted the Vue mount and left
the page on "Loading...", and a custom layer whose `canReset()` could never be true. Stage 6.5
exists so the **generator** finds these, not the player.
1. **Boot.** Serve the folder and open it in a real browser. If console capture is unavailable,
add a collector to `index.html` (a per-game shell — scaffold already patches it):
`<script>window.__bootErrors=[];window.addEventListener("error",e=>__bootErrors.push(String(e.message)));window.addEventListener("unhandledrejection",e=>__bootErrors.push(String(e.reason)));</script>`
**Gate: `__bootErrors` empty and the tree renders** (not stuck on "Loading..." — a Vue mount
abort means one component threw at render time; bisect by opening tabs).
2. **Open every layer tab and every microtab.** A layer that cannot be opened (`layerShown` /
`canReset` deadlock) or a tab whose content throws is found here and nowhere else.
3. **One real interaction per mechanic kind**: prestige once on each layer kind (normal, static,
custom) and confirm the currency rises and survives per the `doReset` contract; buy one
upgrade and one buyable and confirm the number it promises actually moves (dead effects are
invisible to every file-level check); click a clickable; for a board type, complete one match.
4. **Lifecycle probe**: save → reload → values intact; if the game has non-ASCII in `player`
(bilingual microtab keys do), also round-trip a manual export/import (the engine's Export
button is Latin1-only — 07 Part F).
5. **Console clean at the end.** Re-run Stage 5 after every fix; record each browser-only fix
in the brief's **browser-verification section** (§12 of the template) — the pattern list
doubles as regression notes for the next build. The full checklist and the engine traps it
has caught live in `references/design/14 §5`.
**Gate to Stage 7: all five green.** Hand-off to the user is for *balance feel*, not for
discovering that the game does not boot.
---
## Stage 7 — Manual test handoff
Hand the user the 07 Part D checklist plus these four steps:
1. Serve the folder statically and open `index.html` — **the console must show no errors**.
2. Verify in this order: first prestige within minutes; hotkey works; upgrades purchasable and
their effects visible in the numbers; resets cascade per the `doReset` contract; save → reload
intact; export/import and hard reset work; offline time capped by `offlineLimit`; endgame
fires `winText`.
3. Test with offline production **disabled** in options first, and tune `offlineLimit` last
(07 rule 17).
4. NaN or a blank page → 07 Part C crash table, fix, re-run Stage 5.
---
## Failure playbook
Symptom → action for the failures that actually occur. The full table is
[07 Part G](references/core/07-Pitfalls-and-Generation-Checklist.md) — one table, one home.
Part C there is the crash table; Stages 5.5 and 6 above are what stop most rows from happening.
---
## Constraints (do not violate)
- Never edit engine files (`js/technical/*`, `components.js`, `game.js`, `utils*`) — modders
own only `js/mod.js`, `modFiles`-registered files, `js/tree.js`, `css/`, images.
- Never copy numeric code from ExpantaNum-based mods (09 §6) — design yes, calls no.
- The `modInfo.id` chosen at scaffold time is permanent for the game's life.
- Do not balance around offline progress; `offlineLimit` stays small.