Skip to content

Token Gaiden v1 (0.4.0) — full RPG, plan-normalized aging, usage alerts#22

Merged
bjamba merged 6 commits into
mainfrom
productionize/token-gaiden-v1
May 18, 2026
Merged

Token Gaiden v1 (0.4.0) — full RPG, plan-normalized aging, usage alerts#22
bjamba merged 6 commits into
mainfrom
productionize/token-gaiden-v1

Conversation

@bjamba
Copy link
Copy Markdown
Owner

@bjamba bjamba commented May 14, 2026

Ships Token Gaiden RPG as Tokade 0.4.0 — a pixel-art roguelike inside the menu-bar app, fed by your Claude Code telemetry. This PR consolidates the full v1 milestone (foundation, gameplay loop, polish, plan-scale fixes) into one release.

Summary

  • New Games tab at the top level of the Tokade panel. Lists playable cartridges (Token Gaiden RPG today, room for more). Selecting one enters an emulator-screen view; exit lives inside the game's Settings.
  • Full gameplay loop: character creation → live persistent pet → projects-as-regions → real-time encounters + active combat → NPCs / shops / trainers / quests → gear + skills → death + multi-generation inheritance → bloodline Hall of Fame.
  • 45-cosmetic unlock collection across 8 slots (hair / hat / eyewear / cape / shirt / pants / belt / held). Earned through achievements, quest rewards, and rarity-weighted encounter drops. Locked items appear in the Wardrobe as silhouettes with unlock hints.
  • Optional Auto-play mode: pet plays itself end-to-end (heals, claims + accepts quests, upgrades gear, trains, buys SP potions + gear, attacks / casts skills / flees, wanders, dungeons, travels regions). Coverage is comprehensive across every player action.
  • Plan-normalized aging + HP drain — keyed off Δ% of the 5-hour rate-limit budget, not raw tokens, so Pro / Max / Max-5× users see similar wall-clock lifespans. Background tick loop keeps the pet alive with the panel closed.
  • App-level usage alerts: rate-limit threshold crossings (50% / 75% / 90%) and 5-minute token bursts (>500K) surface as macOS notifications. Toggle lives in Tokade's hamburger menu (separate from in-game settings).
  • CRT post-effect (scanlines / phosphor / soft / dot-matrix / edge-fade) applied globally to the game screen.
  • Pixel-art UI primitives: PixelButton, PixelArrowButton, PixelBar, PixelPanel, PixelTextFrame, GameScreen bezel, monospaced gameFont. Every interactive element inside the game uses these.

What's in the diff (high-level)

Area Notable files
Tick economy + plan normalization TickProcessor.swift, TokegotchiState.swift, TokenGaidenStore.swift, TokadeApp.swift
World + regions Region.swift (project-root walk), RegionMapCard.swift (top-10 by recency), WorldMapArt.swift, BiomeArt.swift
Encounters + combat Encounter.swift, ActiveCombat.swift, ActiveBattleCard.swift, Skill.swift, MonsterArt.swift
Economy + content NPC.swift, Shop.swift, Quest.swift, Gear.swift, Items.swift, Achievements.swift
Cosmetics CosmeticCatalog.swift, SpriteComposer.swift, SpriteView.swift (with composeLayersOnly for silhouettes)
Autopilot AutoPlay.swift
Notifications Notifier.swift, UsageAlerter.swift
UI shell GameScreen.swift, GamesTab.swift, TokenGaidenTab.swift, PixelBar.swift, BannerRow.swift, FlowLayout.swift
Assets ~147 baked cosmetic matrices, 30 monster matrices, 6 biome tiles, world overworld, 3 banner variants

Tests

99 XCTest cases. New coverage areas: matrix parsing & composition, palette + cosmetic catalog, region project-root walk, NPC interactions (shop / haggle / train), quest engine, gear & item resolution, skill effects, encounter math, death + inheritance, plan-budget wear pathway.

Test plan

  • swift build && swift test clean
  • ./scripts/check.sh passes guardrails
  • ./install.sh builds, ad-hoc signs, and launches /Applications/Tokade.app
  • Existing Tokade tabs (Budget / Models / Trends) still work
  • Games tab launches Token Gaiden RPG cartridge
  • Fresh hatch → ticks through HP drain + aging as Claude Code activity flows
  • Cosmetic unlock toast + silhouettes in wardrobe behave as expected
  • Auto-play feeds, fights, trains, equips, and travels without manual input
  • Usage alerts fire at 50/75/90% rate-limit and on 500K-token bursts
  • Existing pets migrate cleanly (no save-schema crash)

Notes for review

  • Save schema additions (lastUsedPercentage, lastEncounterAt, discoveredCosmetics) are all optional; pre-0.4 saves load and migrate forward via TokenGaidenStore.load()migrateDiscoveredCosmetics().
  • Region.identifier(for:) now walks up the cwd to a project root marker, so sub-folders of the same project collapse into one region. Existing save state with per-subfolder identifiers stays in state.world but only the top 10 by lastActiveAt paint on the map.
  • All gameplay rate-caps (encounter cooldown, drop rolls, autopilot decisions) are tuned for the Max-1× / Max-5× spread; calibration may want tuning once we see Pro telemetry.

🤖 Generated with Claude Code

bjamba and others added 4 commits May 13, 2026 17:05
Implements the Layer 1 + sprite foundation from docs/02-design/TOKADE_TAB.md
and the ADR-0005 architecture decisions. Encounters, regions, quests, combat,
and the death/inheritance loop will land in subsequent PRs.

What's in
- Sources/Tokade/TokenGaiden/
  - Matrix.swift           — SpriteMatrix value type + parser
  - Palette.swift          — PaletteRole enum + role→RGB mapping; default
                             "Boba" preset + character creator swatches
  - SpriteRenderer.swift   — matrix + palette → NSImage at integer upscale
  - SpriteView.swift       — SwiftUI view animating frames at chosen fps;
                             BundledSprites loads idle / walk-a / walk-b
  - TokegotchiState.swift  — full Codable state model with derived hpMax/spMax;
                             newStarter() factory and isAgedOut / isCritical
  - TokegotchiSave.swift   — actor for atomic 0600 reads/writes to
                             ~/.tokade/games/tokegotchi.json
  - TickProcessor.swift    — pure-functional UsageEvent → game effect:
                             HP drain (Haiku 1/10K, Sonnet 1/5K, Opus 1/2K),
                             age advance (×0.5 / ×1.0 / ×2.0),
                             tool→item drops, critical/death detection
  - TokenGaidenStore.swift — @mainactor @observable, idempotent over re-reads
                             via per-message accountedTokens accumulator
  - TokenGaidenTab.swift   — character creator + alive layout with vitals,
                             stats line, sprite view, recent drops

- Sources/Tokade/Resources/sprites/{idle,walk-a,walk-b}.matrix
  Bundled animation frames baked from design/tokegotchi/animation/

- MenuView gains a fourth tab ("Tokade") that hosts the game.
- TokadeApp constructs a TokenGaidenStore and calls load() on launch.
- "Erase history…" now also erases Token Gaiden state.

Tests (22 new XCTest cases, 62 total pass)
- SpriteMatrixTests       — comment skipping, ragged-row detection, real
                             matrix from design/ folder parses cleanly
- PaletteTests             — role glyph uniqueness, hex parsing, swatch
                             preset counts
- TokegotchiStateTests     — derived hpMax/spMax, clamp, Codable round-trip
- TickProcessorTests       — per-model drain + age, tool drop mapping,
                             enteredCritical fires once, natural death
- TokenGaidenStoreTests    — tick idempotence over re-reads, delta-only
                             charging when token totals grow

Guardrails + ops
- CLAUDE.md  + scripts/check.sh enforce 0o600 in TokegotchiSave.swift
- appBundledResource(_:ext:) is now module-internal so TokenGaiden uses
  the same Bundle.module workaround pattern as the rest of the app
- .swiftformat excludes design/ — design-folder helper Swift scripts
  (pixelate.swift, bake.swift, render_matrix.swift) are authoring tools
  and shouldn't share the production lint config
- CHANGELOG.md [Unreleased] notes the M0 foundation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the sprite layering system that lets equipped cosmetics actually
appear on the rendered Tokegotchi. M0 shipped the sprite + state pieces
but composed nothing on top of the naked base — players saw the same
default sprite regardless of their hair-style pick. Fixed.

What's new
- SpriteComposer.swift — pure-functional layering of matrices over a
  base. Transparent cells pass through; later layers paint over earlier.
- 14 cosmetic matrix files bundled in Resources/sprites/ (flat layout
  with tg-<slot>-<name>.matrix convention because SwiftPM .process
  resources don't preserve subdirectories).
- BundledSprites.compose(...) layers cosmetics over base frames in the
  ADR-0005 z-order: cape → pants → belt → shirt → hair → eyewear → hat.
- TokenGaidenStore.equipCosmetic(slot:name:) persists slot changes.
- WardrobeSheet — modal that lets the player swap cosmetic slots live
  from the v1 bundled inventory. (Inventory-gating from monster drops
  lands in a later PR.)
- TokegotchiState.newStarter respects the chosen hair style — was
  previously hardcoded to "horns" via defaultCosmetic.

Naked-base bake
- design/tokegotchi/bake-cosmetic.sh — bakes a single SVG fragment to a
  standalone matrix using the same palette substitution render.sh uses.
- The three bundled animation frames (idle/walk-a/walk-b) are re-baked
  with all cosmetic slots empty, so layering doesn't double up.

Tests (+6, 68 total pass)
- SpriteComposerTests: empty layers, transparent pass-through, z-order
  with three layers, mismatched-dimension skip, nil-tolerant ordered
  layers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the region-tracking spine from docs/02-design/TOKADE_TAB.md Layer 3.
Each Claude Code cwd resolves to a stable region identifier and gets a
"flavor" seeded once on first visit by sniffing project marker files.
Reputation grows as the player accumulates events in a region.

What's new
- Region.swift — pure utility functions:
  - identifier(for:) maps a cwd to a home-relative path, with stable
    fallbacks for paths outside $HOME
  - flavor(for:) reads project files (Package.swift / Cargo.toml /
    pyproject.toml / package.json / go.mod) to assign a Flavor enum
    (stonework / ironFortress / gardenVillage / bazaar / openSteppe /
    wilderness) with player-visible displayName
- TokegotchiState.World gains two optional fields (Codable-compatible
  with M0 saves): flavors [String: Region.Flavor] and eventCounts
  [String: Int]. Optional types so the M0 schema still loads.
- TickProcessor now updates currentRegion, seeds flavor on first visit,
  increments eventCount, and grants +1 reputation per 50 events (cap 100)
- TokenGaidenTab heroCard shows "Region: code/tokade · Stonework Town · rep 12"

Tests (+6, 74 total pass)
- RegionTests: home-prefix stripping, trailing-slash, home-itself,
  outside-home collapse-to-last-two, flavor detection via marker files,
  end-to-end reputation accumulation through TickProcessor.

What's NOT here yet (per design doc)
- LoC-step discovery thresholds for revealing NPCs/dungeons/towns
- Fast-travel between visited regions
- Region-specific monster + NPC populations

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Turns the v1 foundation into a thing the player can actually interact
with. M0 plus the prior two commits gave us telemetry → state → sprite.
This commit adds the player-facing game-y loop on top.

Items (Sources/Tokade/TokenGaiden/Items.swift)
- ItemCatalog: explicit catalog of all v1 items with id, display name,
  glyph, and Kind (food / SP potion / stat boost / scrap).
- ItemUsage.use: pure-functional consume. Food heals (clamped to hpMax),
  potions restore SP, stat-boost items raise the named stat by 1, scrap
  sells for 2 gold. Returns updated state + a Result for UI feedback.
- TokenGaidenStore.useItem(_:) wires it to UI, persists, populates
  lastResults for toast rendering.
- TickProcessor now drops bread alongside the existing DEX item on
  Edit/Write/NotebookEdit calls — every file edit produces visible food.

Encounters (Sources/Tokade/TokenGaiden/Encounter.swift)
- One Encounter struct + a roster per Region.Flavor (e.g. Stonework Town
  has Granite Golem + Loose Brick; Bazaar has Pickpocket + Hawker).
- EncounterEngine.choose: picks the highest beatable monster given the
  player's stats; falls back to the weakest if all are beatable.
- EncounterEngine.resolve: passive auto-resolve. If the player can kill
  the monster in ≤6 turns at current ATK = STR + DEX/2, victory pays EXP
  + gold. Otherwise flees with no penalty.
- TickProcessor triggers an encounter every 25 events in the current
  region using region flavor + event count as deterministic salt.

Achievements (Sources/Tokade/TokenGaiden/Achievements.swift)
- 11 achievements covering hatch, age milestones, per-stat 10 thresholds,
  gold-100, three-distinct-regions, reputation-50, and elder-status (70%
  of lifespan).
- Persisted as inventory items under the reserved "achievement:" prefix
  so the save schema doesn't need to change.
- AchievementCatalog.newlyEarned runs after every tick; results surface
  as toasts.

UI (Sources/Tokade/TokenGaiden/TokenGaidenTab.swift)
- New Inventory card listing every owned item with a per-stack Use button,
  per-effect description (e.g. "+25 HP"), and emoji glyph.
- New Achievements card showing earned achievements with descriptions.
- EXP and gold counters next to the Wardrobe button in the hero row.
- prettyResult expanded to render encounter + achievement TickResults.

Tests (+11, 85 total pass)
- ItemsTests: food healing + clamp, missing-item, stat boost, scrap →
  gold.
- AchievementTests: first-light immediate fire, stat threshold trigger,
  already-earned no-refire.
- EncounterTests: victory rewards, flee when outmatched, choose returns
  beatable monster.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bjamba bjamba marked this pull request as ready for review May 14, 2026 00:24
Token Gaiden RPG ships as a top-level Games tab in Tokade. Full gameplay
loop fed by Claude Code telemetry: project-typed regions, real-time
encounters + active combat, NPCs / shops / trainers / quests, gear and
skill systems, 45-cosmetic unlock collection, multi-generation
inheritance, and an opt-in autopilot.

Aging + HP drain rewired to track Δ% of the 5-hour rate-limit budget
instead of raw tokens, so Pro / Max / Max-5× users see similar
wall-clock lifespans. Background tick loop keeps the pet alive with the
menu bar panel closed. App-level usage alerts (rate-limit thresholds,
5-minute token bursts) live in Tokade's hamburger menu, separate from
in-game settings.

99 XCTest cases passing.
@bjamba bjamba changed the title Token Gaiden M0: matrix sprite system + tick economy foundation Token Gaiden v1 (0.4.0) — full RPG, plan-normalized aging, usage alerts May 18, 2026
Mechanical formatting fixes from `swiftformat .`: number grouping,
single-line function-body wrapping, conditionalAssignment for if/switch
returns, blankLinesBetweenScopes, sortImports, andOperator (&& → comma
in guard/if conditions), braces, opaqueGenericParameters,
redundantNilInit, redundantViewBuilder, docComments, and
noForceUnwrapInTests (XCTUnwrap replacement). No behavior changes.
99 tests still pass; ./scripts/check.sh clean.
@bjamba bjamba merged commit 63b2d10 into main May 18, 2026
1 check passed
@bjamba bjamba deleted the productionize/token-gaiden-v1 branch May 18, 2026 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant