Token Gaiden v1 (0.4.0) — full RPG, plan-normalized aging, usage alerts#22
Merged
Conversation
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>
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Gamestab 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.PixelButton,PixelArrowButton,PixelBar,PixelPanel,PixelTextFrame,GameScreenbezel, monospacedgameFont. Every interactive element inside the game uses these.What's in the diff (high-level)
TickProcessor.swift,TokegotchiState.swift,TokenGaidenStore.swift,TokadeApp.swiftRegion.swift(project-root walk),RegionMapCard.swift(top-10 by recency),WorldMapArt.swift,BiomeArt.swiftEncounter.swift,ActiveCombat.swift,ActiveBattleCard.swift,Skill.swift,MonsterArt.swiftNPC.swift,Shop.swift,Quest.swift,Gear.swift,Items.swift,Achievements.swiftCosmeticCatalog.swift,SpriteComposer.swift,SpriteView.swift(withcomposeLayersOnlyfor silhouettes)AutoPlay.swiftNotifier.swift,UsageAlerter.swiftGameScreen.swift,GamesTab.swift,TokenGaidenTab.swift,PixelBar.swift,BannerRow.swift,FlowLayout.swiftTests
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 testclean./scripts/check.shpasses guardrails./install.shbuilds, ad-hoc signs, and launches/Applications/Tokade.appNotes for review
lastUsedPercentage,lastEncounterAt,discoveredCosmetics) are all optional; pre-0.4 saves load and migrate forward viaTokenGaidenStore.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 instate.worldbut only the top 10 bylastActiveAtpaint on the map.🤖 Generated with Claude Code