Game-engine rules for Token Tamers — cycle policies, molts/rebirth, evolution, traits, grade rolls, lineage, Archive, determinism requirements. Use when working under packages/core or changing any gameplay mechanic.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add zivkong/token-tamers --skill develop-game-engine --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Develop Game Engine?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/zivkong-develop-game-engine)More formats (shields.io, HTML) on the badges page.
---
name: develop-game-engine
description: Game-engine rules for Token Tamers — cycle policies, molts/rebirth, evolution, traits, grade rolls, lineage, Archive, determinism requirements. Use when working under packages/core or changing any gameplay mechanic.
---
# Develop the game engine (packages/core)
Source of truth: `docs/design/lifecycle-and-cycles.md`,
`docs/design/evolution-grades-lineage.md`, and `docs/design/dna-hash-battles.md`. The engine is PURE and
DETERMINISTIC: no I/O, no `Date.now()`/`new Date()`/`Math.random()` (ESLint enforces) —
time enters as event timestamps, randomness via the seeded RNG (`src/helpers/rng.ts`,
mulberry32, serializable state). Same saved state + same events + same clock ⇒
identical results, forever. The engine is provider-blind: it consumes only normalized
`UsageEvent`s and cycle events.
## The canonical cycle rule (never violate)
- **Evolution cycle = the 5-hour session window.** Every closed window containing
usage fires a MOLT_CHECKPOINT — the ONLY moment a pet can change stage, roll a
trait, mutate, evolve, or attempt a grade-up. Nothing evolves between molts. A
molt is the OPPORTUNITY to evolve, not a guarantee: each stage must accrue its
**maturity** requirement (`pet.stageMolts` vs `STAGE_MATURITY`) and clear any
quality gate (`STAGE_GATE`, e.g. prime→apex needs grade ≥ B) before it advances —
pacing the egg→apex climb across ~5 active days. See `engine/maturity.ts`.
- **Weekly cycle = rebirth, nothing else.** The week boundary fires REBIRTH only:
ascension, legacy scoring, Archive record, inheritance roll, new egg. The pet's
final form is whatever it became at the week's **last molt** — rebirth never
evolves it.
- **Egg fast-hatch (the sole exception).** Each week's egg→sprite hatch fires on a
bonus checkpoint ≈10 min after that week's first usage (`EGG_HATCH_MS`,
`eggHatchMolts`), not at a 5-h close. It is an ADDITIVE `{hatch:true}` molt layered
on the normal window chain (one per week — every generation starts as an egg at a
rebirth); `replayMolt` makes it act only while the pet is an egg, else a no-op, so
the normal windows / pending buffer / determinism are untouched. It hatches +
rolls like a molt but skips diet and `updateBaseline` (normalization comes only
from real 5-h windows). ONLY the hatch bypasses the 5-h rule; every later molt
obeys it. Keep the checkpoint a pure fn of (events, weekAnchor) — never gate the
window SHAPE on pet state (it reshapes the chain and breaks replay==resume).
## Cycle policy (ONE pet-global clock → abstract events)
The cycle is pet-global, never per adapter: `EngineConfig.cycle` is a `CycleConfig
{ policy: 'subscription' | 'static'; anchorAdapter?: string; weekAnchor }`. Molts +
rebirths are derived ONCE over the merged event stream (`deriveCycleEvents`), so a
single molt covers the whole pet (grade/trait/evolution roll once per window).
| Abstract event | Subscription policy | Static policy |
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------- |
| MOLT_CHECKPOINT | Inferred 5-h window close from usage gaps in the ANCHOR adapter's stream alone | Fixed 5-h windows from `weekAnchor`; molts only if used |
| REBIRTH | Every 7 days from the EFFECTIVE week anchor (see below) | Every 7 days from the effective week anchor |
**Week anchor — effective anchor + real-reset sync + reconcile.** `weekRebirths` never
uses the raw `weekAnchor` directly: `effectiveWeekAnchor(events, weekAnchor)` keeps its
PHASE but slides it back by whole weeks to the tile containing the first event, so a
FUTURE anchor (a freshly-init'd "next Monday", or an unobserved reset) can never short-
circuit rebirth (`now < weekAnchor`). The whole-week shift leaves the weekly phase — and
static 5-h tiling, which keeps the raw anchor (`WEEK_MS % WINDOW_MS ≠ 0`) — unchanged.
The PHASE itself is the player's **real** subscription reset when captured: the cli's `tt
statusline` records `rate_limits.seven_day.resets_at` to `~/.tokentamers/usage.json` and
catch-up feeds it as `weekAnchor` (in-memory; deterministic — core still sees only a
number). `Engine.reconcile(now)` (pure `overdueRebirthEvent` in `engine/reconcile.ts`) is a
one-time, idempotent load-time repair: a pet whose `simulatedTo` already slipped past a
boundary without rebirthing gets the owed catch-up rebirth. Kept OUT of `advanceTo` (so
replay==resume holds); the cli runs it after `ingest`, before `advanceTo`.
**Manual rebirth + UI forecasts.** `Engine.rebornNow(now)` is the Apex "Reborn Now" player action:
the same archive + fresh-egg flow as a weekly rebirth, forced at `now`. Like `reconcile`/equips it is
NOT derived from events/clock and is kept OUT of `advanceTo`; it consumes **no RNG** (rebirth is
RNG-free) and does NOT move the weekly clock — the fixed boundary still fires on schedule, so the new
egg gets the week's remainder. A no-op on a pre-hatch egg; the grade-≠-S confirm guard lives in the
TUI. `nextMoltCloseAt(now)` / `nextRebirthAt(now)` are pure forecasts (in `cycle/index.ts`,
mirroring the window/week math) the cli turns into the pet page's live `secsToMolt`/`secsToRebirth`
countdowns — read-only, no state change.
Adapters are pure data sources (`{ provider, paths }`) — no per-adapter plan/policy;
API- and subscription-billed usage in one adapter are undifferentiated essence
(invariant 3). Under the subscription policy only the anchor adapter opens/closes
windows; every adapter still feeds essence into the open window. Multi-provider players
feed ONE pet: essence is normalized per adapter against that adapter's OWN baseline, then
combined (`combinedEssenceRatio`) — a second agent diversifies diet, never inflates power.
The v3→v4 schema bump moved the cycle from per-adapter to `UserConfig.cycle` (cli config
store migrates old configs forward).
## Signal mapping
| Real-world signal | Game meaning |
| -------------------------------- | -------------------------------------------------------------------------- |
| Token consumption (any provider) | Nutrition/essence (baseline-normalized) |
| Model-ID mix | Diet → House/species (identity ONLY) |
| Per-install salt + `houseBias` | Cosmetic House spread at hatch (identity; not mechanics, not the molt RNG) |
| Session window close | Molt |
| Week boundary | Rebirth |
| Riding a window to its cap | Marathoner trigger |
| Hitting weekly limit exactly | Rare Limitbreaker evolution (subscription policy only) |
| Week of zero usage | Dormant (cocoon, not death) |
## Weekly arc & weather
- Three acts: **Growth** (days 1–3) molts and traits accrue; **Bloom** (days 4–6) the
form matures and one random molt fires the weekly **Bloom event** — a guaranteed
rare roll; **Twilight** (final ~24h) legacy score crystallizes and the UI previews
rebirth inheritance. Form is already final after the week's last molt.
- **Weekly weather:** a deterministic seed from the ISO week number biases trait
rates ("Storm Week: Sprinter rolls doubled") — shared by every machine, fully
offline, no server.
- Molt evaluation inputs: session count, gap rhythm, time-of-day spread, window-cap
proximity, tool/lang diversity, streak vs burst, adapter diversity.
## Evolution
- Stage track: egg (Mote) → sprite → rookie → evolved → prime → apex.
Behavioral branching from rookie on; rares, patterns, rising mutation chance at
later molts. Solo reaches Apex.
- **Maturity pacing (lever A+B).** Each stage holds a maturity clock
(`pet.stageMolts`, reset on hatch and every evolution); it may evolve only once
`stageMolts ≥ STAGE_MATURITY[stage]` (sprite 1, rookie 2, evolved 3, prime 4 —
rising, so growth slows) AND `evolutionGateMet` (prime→apex needs grade ≥ B).
`growthProgress(state)` exports a SPOILER-FREE readout (fill frac + flags, never
stage/count) for the Pet page's abstract "Grow" cue. Empty windows still never
molt; both "≤1 stage per molt" and "grade never downgrades" still hold. All pure
fns of persisted state ⇒ replay==resume. Constants/logic live in
`engine/maturity.ts`; bumped `SCHEMA_VERSION` (cli store migrates `stageMolts`).
- Branching is DATA (`evolvesTo[].when` BranchConditions): rookie fork by rhythm
(steady/bursty); evolved 3-way by dominant trait class (endurance = Marathoner/
Deepdiver, tempo = Sprinter/Swarm, breadth = Polyglot/Switcher); prime 3-way by
consistency band vs own baseline; apex 2-way by lifetime arc (early-peak/late-bloom).
- One trait roll per molt, max ~5 slots. Triggers (all model-neutral): Marathoner
(window ridden to cap), Sprinter (short bursts, long gaps), Polyglot (3+ langs),
Nightshade (majority after midnight), Daybreaker (pre-9am), Switcher (model change
mid-session — rewards mixing, never punishes mono-model), Deepdiver (one long
thread), Swarm (many parallel short sessions), Polyhost (2+ adapters in a window).
- Patterns (checked every molt, locked at week's final molt): Marathoner+Nightshade →
Vigil; Sprinter+Swarm → Tempest; any 4 distinct traits → Prism; Polyhost+Switcher →
Chimera.
- Mutations: ~5% per molt (palette shift, off-line trait, or stat swap).
- Rhythm variants (Burnout/Disciplined/Nocturne): cosmetic variants from Evolved on.
## Grade rolls (monotonic, transparent, never guaranteed)
- Hatch at C; one roll per molt, one step at a time: C→B 25%, B→A 10%, A→S 3% base.
- Activity modifier ×0.5–×2.0 from molt-eval signals ONLY (consistency vs own
baseline, trait synergy, rhythm quality, diversity). Token volume and model id
NEVER enter the modifier (pillar 2). A→S hard-capped at ~6%.
- Capped vitality bonus (hybrid growth): the chance is `gradeRollChance(from, modifier,
totalTokens)` = `base*modifier + vitalityBonus(totalTokens)`, A→S clamped to `A_TO_S_CAP`.
`vitalityBonus` ramps linearly to `VITALITY_MAX_BONUS` (0.15) at `VITALITY_FULL_TOKENS`
(200M) then clamps. This additive bonus is the ONLY place absolute volume touches power;
the modifier stays volume-blind and the A→S cap applies AFTER it. `gradeRollChance` is the
single source of truth — `rollGrade` (the real molt) and `gradeOdds` (the UI forecast) both
call it, so they can never drift. Deterministic (pure fn of the window's raw tokens).
- **UI forecast:** `gradeOdds(state, pending?)` returns the next roll `{from, to, chance,
capped}` (null at the S cap) — with the open window's events it folds in the live modifier +
vitality (what the molt will roll); with none it reports the published base odds. The pet
page's `Odds` row shows ONLY this current→next forecast (the only roll that can fire next).
Pure + deterministic: reads state + event timestamps, never the clock.
- Grade NEVER downgrades — not from a bad window, not from Dormancy. No pity
guarantee. Record `lastGradeRoll` (odds shown in UI — transparency defuses RNG
resentment). A success is a Gradeshift (cutscene moment).
## Rebirth, lineage, Dex records (unified Archive)
- Stat carry-over: 30% base +10% per stage tier reached, cap 70%; inherited trait =
most-repeated. New egg starts at C (lineage perks may sweeten roll odds, never the
starting grade, never to certainty).
- Lineage perks: species affinity by lineage; 3× Prism ancestors → **Kaleido** egg;
**Progenitor** flag marks DNA donors. Perks sweeten roll odds only, never grades.
- **Dex record store (`state.dexRecords`) is the source of truth** — each species keeps
its **top-3** snapshots, ranked grade-desc then stat-total (`engine/dex-records.ts`).
Capture (`engine/snapshot.ts` → `tryCaptureSnapshot`) fires at every molt close,
evolution, and rebirth via the engine's private `capture()`; the `dex_record` effect
reports new entries. Snapshots clone stats/arrays (self-contained). Capture must stay
deterministic (total-order insert, `recordedAt`-asc final tiebreak, equal-peak dedupe)
so replay-from-scratch == resume-from-snapshot.
- The legacy `state.archive` (one strictly-best record per species, rebirth-only) is
STILL written as a back-compat mirror — the grade/house achievements read it, and the
Archive view derives best-per-species from `dexRecords` via `bestSpeciesRecords` (each
record's `top[0]` ≡ the old strictly-best). Don't remove `archive`/`lineage`.
- Stats PWR/SPD/WIS/GRT; equal total budget per stage across species (horizontal
evolution: different builds, never better builds).
## DNA codec, readiness gate, graft potency (M2.1 encoder shipped)
- **Codec** lives in `src/dna/` (NOT engine): `encodeDna(snapshot, {speciesNum})` → an opaque
`TTX<v>-XXXX-…` license-key token, pure inverse `decodeDna`. Deterministic (same snapshot ⇒
same code — required for live Dex display + battle replays). No `node:*`/`Buffer` (invariant 4)
— hand-rolled varint + Crockford base32 + a mulberry32 whitening keystream in `dna/payload.ts`;
body = `[FNV tag:4][whiten(payload)]`, the payload a fixed/append-only varint stream + a reserved
`extLen` TLV area, enums encoded as indices into the **append-only** `dna/registry.ts` tables
(invariant 7). Whitening is obfuscation, NOT encryption (the seed travels in the tag). A golden
test locks the byte layout (never edit existing golden codes — add new ones on a schema bump);
`decodeDna` never throws on newer schemas / unknown ids / extra trailing data.
- **Readiness gate** (`engine/maturity.ts`): a snapshot is battle/graft-eligible only once
`stage` ≥ `BATTLE_READY_STAGE` (Evolved). `isBattleReady`/`isGraftReady`/`stageMature` are
pure, stage-only (so a decoded foreign code's readiness is tamper-evident). Identity-based,
never model-based (invariant 3).
- **Graft potency** (`engine/graft.ts`, `GRAFT_POTENCY` in constants): donor-grade scaled,
C = 0 … S hard-capped at +0.08 on both grade-up chance and stat boost. Forward spec for the
**Season 1 ("Crossbreed") grafting/fusion engine (M2.3)**; `graftPotency`/`graftPotencyTier`
exposed now for the Dex UI. Grade-based only, capped below base odds — never model/volume-derived.
> **Season scoping (2026-06-16).** Engine work is split by Season: **Season 0 — the Battle engine
> is SHIPPED** in `src/battle/` (`simulateBattle(a, b, ruleset)` = deterministic
> `f(combatantA, combatantB, ruleset.version)`, the circular House type-wheel + trait procs read
> from `ContentPack.battle` content data, the battle-only grade stat-floor `GRADE_STAT_FLOOR`;
> PURE + seeded, consumes a decoded DNA snapshot read-only). **Season 1 — the grafting/fusion
> engine** (consumes `GRAFT_POTENCY`, trait-pool splice, grade carry, S-spliced marker,
> one-per-life, hybrid/fusion species) is the next core build. The readiness gate above guards
> both. Full spec: `docs/design/dna-hash-battles.md` §9 (graft/fusion) + §11 (battle).
## Testing requirements
Determinism property tests (replay-from-scratch == resume-from-snapshot; idempotent
re-advance), monotonic grades, molt-only evolution, window/week boundary math,
normalization invariance (10× volume or different same-House model ⇒ identical
trajectory). DNA codec: round-trip every field, a golden byte-layout lock, tamper →
`sigValid:false`, and newer-schema/garbage never throws (`__tests__/dna.test.ts`).
Dex records: top-3 keep/replace, ranking, equal-peak dedupe (`dex-records.test.ts`).
Battle code (M2) must be `f(hashA, hashB, ruleset_version)` — replays reproducible forever.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!