Skip to content

feat: 분기 진화 시스템 + 신규 포켓몬 5종 (M1-PR1)#5

Closed
ThunderConch wants to merge 4 commits into
masterfrom
feat/m1-branching-evolution
Closed

feat: 분기 진화 시스템 + 신규 포켓몬 5종 (M1-PR1)#5
ThunderConch wants to merge 4 commits into
masterfrom
feat/m1-branching-evolution

Conversation

@ThunderConch
Copy link
Copy Markdown
Owner

Summary

  • 분기 진화 시스템: evolves_to: BranchEvolution[] 지원. 분기 포켓몬은 auto-evolve 차단 → evolution_ready 플래그 → 사용자 선택 진화
  • 신규 포켓몬 5종: Ralts(#280), Kirlia(#281), Gardevoir(#282), Snorunt(#361), Glalie(#362) — PokeAPI에서 실제 에셋 다운로드
  • 기존 포켓몬 수정: Burmy(#412) 분기화, Gallade(#475)/Froslass(#478) line 배열 보정
  • 전체 112종, 358 테스트 통과 (신규 18 + 기존 340 회귀 없음)

Changed Files

영역 파일 변경
Types src/core/types.ts BranchEvolution, PokemonData.evolves_to, PokemonState.evolution_ready/options
Logic src/core/evolution.ts checkEvolution() 분기 감지, getEligibleBranches(), applyBranchEvolution()
Data data/pokemon.json +5종, 수정 3종
Data data/regions.json Ralts→region 2, Snorunt→region 3
i18n data/i18n/{ko,en}.json 신규 포켓몬 이름
Assets cries/, sprites/ 5종 cry + sprite (terminal, braille, kitty, sixel, iterm2)
Tests test/branching-evolution.test.ts 18개 신규 테스트
Tests test/{pokedex,pokemon-data,region}.test.ts 107→112 카운트 업데이트

Test plan

  • npm test — 358 pass, 0 fail
  • 기존 단일 경로 진화 회귀 테스트 (387→388 등)
  • 분기 진화 차단 테스트 (Kirlia, Snorunt, Burmy)
  • getEligibleBranches() 조건 충족/미충족
  • applyBranchEvolution() state/config 변이
  • 에셋 파일 존재 확인 (cry, terminal sprite)

M1-PR1 of v0.1.0 spec. M1-PR2 (CLI + notifications + hooks) follows after merge.

🤖 Generated with Claude Code

ThunderConch and others added 4 commits April 2, 2026 14:00
- v0.1.0 스펙: 분기진화, 알림, 이벤트 인카운터, 대시보드, 도감보상, 전설해금, 파티관리
- 영문 표시명 Pokeball → Poké Ball (공식 명칭)
- 한국어 표시명 포켓몬볼 → 몬스터볼 (공식 명칭)
- 코드 내부 키 pokeball 유지 (migration 불필요)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Planner → Architect → Critic 3라운드 컨센서스:
- 17 tasks, 3 milestones (M1: 분기진화+알림, M2: 이벤트+대시보드, M3: 도감보상+파티)
- ADR: evolves_to가 line[]을 오버라이드, Gen 4 분기만 우선
- 신규 포켓몬 5종 추가 (Ralts/Kirlia/Gardevoir/Snorunt/Glalie)
- 전설 8종은 마일스톤 카운트 제외 (112 non-legendary = complete)
- M1 2-PR 분할 (진화 로직 분리)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- BranchEvolution 타입, PokemonData.evolves_to, PokemonState.evolution_ready/options 추가
- 신규 포켓몬: Ralts(#280), Kirlia(#281), Gardevoir(#282), Snorunt(#361), Glalie(#362)
- 기존 수정: Burmy(#412) 분기화, Gallade(#475)/Froslass(#478) line 보정
- checkEvolution(): 분기 진화 auto-evolve 차단 + evolution_ready 플래그
- getEligibleBranches(): 조건별 분기 목록 반환
- applyBranchEvolution(): 사용자 선택 진화 적용
- PokeAPI에서 cry/sprite 에셋 다운로드 + braille/terminal/png 프로토콜 생성
- 테스트 358개 전부 통과 (신규 18 + 기존 340 회귀 없음)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- evolution.ts: zombie state 가드 → 코멘트 문서화 (파티만 처리하므로 안전)
- pokemon.json: Ralts(#280)에 evolves_to: "281" 추가 (일관성)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ThunderConch
Copy link
Copy Markdown
Owner Author

M1-PR2 (#6)에 이미 포함된 변경사항입니다. 중복 PR 닫기.

ThunderConch added a commit that referenced this pull request Apr 8, 2026
- Victory handler: re-read state inside withLockRetry to avoid
  overwriting concurrent hook changes to state.json (#5)
- Move swap: wrap state mutation in withLockRetry with fresh
  readState/writeState to prevent lost updates (#11)
- Init handler: add try/catch with descriptive error messages
  for state.json and config.json parsing (#20)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ThunderConch added a commit that referenced this pull request Apr 9, 2026
- Move readCodexTotalTokens() inside withLockRetry to fix TOCTOU (concurrency #3)
- Codex-only turns: award Codex XP even when Claude deltaTokens <= 0 (gen-addition #1)
- Advance checkpoint by consumed tokens only, retain sub-threshold remainder (gen-addition #2)
- Skip Claude XP (floor/rest bonus) on Codex-only turns to prevent false awards
- Log one-time warning when node:sqlite unavailable (stability #5)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ThunderConch added a commit that referenced this pull request Apr 9, 2026
- Move readCodexTotalTokens() inside withLockRetry to fix TOCTOU (concurrency #3)
- Codex-only turns: award Codex XP even when Claude deltaTokens <= 0 (gen-addition #1)
- Advance checkpoint by consumed tokens only, retain sub-threshold remainder (gen-addition #2)
- Skip Claude XP (floor/rest bonus) on Codex-only turns to prevent false awards
- Log one-time warning when node:sqlite unavailable (stability #5)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ThunderConch added a commit that referenced this pull request Apr 20, 2026
…lback

Live scenario testing surfaced two related failures:

1. cmdEvolve only dispatched through getEligibleBranches, so pokemon
   whose data.evolves_to is a string (or legacy line[stage+1]) — i.e.
   every non-branching pokemon — hit "no eligible" and returned
   without invoking executeEvolve. That defeated the Stop-hook
   AskUserQuestion flow for any single-chain pokemon: the user would
   see the prompt, click the target, and nothing would happen.

   Add a dedicated single-chain branch in cmdEvolve that routes
   through checkEvolution (no state → returns a validated
   EvolutionResult) and then executeEvolve, which already dispatches
   to applySingleChainEvolution under the hood. The branch runs
   ahead of the getEligibleBranches path so branching pokemon still
   use the original flow.

2. checkEvolution's string-evolves_to path only called
   ensurePokemonInDB for explicit cross-gen references like
   "gen1:25". Plain numeric IDs that happened to live only in another
   generation (e.g. Charmeleon #5 on a gen4-active save) fell through
   and returned null because targetData was undefined. Same issue on
   the legacy line[stage+1] path. Both paths now use the
   ensurePokemonInDB fallback so cross-gen targets resolve.

Also seed the required evolution stones on the multi-3 and
overflow-5 scenarios so Pikachu (thunder-stone) and Eevee
(water/thunder/fire stones) can actually evolve when chosen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ThunderConch added a commit that referenced this pull request Apr 20, 2026
…est harness (#57)

* feat: prompt user for evolution via Stop-hook AskUserQuestion

Stop hook scans party for pokemon ready to evolve (branch + single
chain) and emits {decision:"block", reason} to force Claude to call
AskUserQuestion instead of auto-evolving or silently staging a flag.
User selects a target, Claude runs `tokenmon evolve <name> <target>`.
Refuse sets evolution_prompt_shown, which holds the prompt until the
user manually runs `/tkm evolve` to clear it.

- evolution.ts: single-chain now uses the same flag-based flow as
  branch evolutions; state-missing callers keep the original return
  signature so existing tests remain green
- stop.ts: post-XP scan emits block JSON first, then persists the
  prompt_shown flag under lock (duplicate prompt > silent loss on
  crash); lock failures are logged rather than swallowed
- markEvolutionReady helper dedups the three single-chain paths in
  checkEvolution
- notifications.ts + status-line.ts: skip the ready hint once the
  prompt has fired
- test/e2e: harness verifies block JSON and flag persistence via an
  isolated CLAUDE_CONFIG_DIR; tmux full-session harness remains TODO

* fix(stop): preserve system_message on evolution block emission

The evolution block path in stop.ts previously emitted
{decision:"block", reason} but discarded any system_message that was
populated in the same stop turn — level-up, catch, and achievement
notifications would silently disappear the moment an evolution
triggered. Merge the accumulated system_message into the block output
so user-facing messages persist alongside Claude's block instruction.
systemMessage is user-facing only per the Claude Code hooks spec, so
it does not interfere with the reason field Claude consumes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(dev): add /tkm:test-evolve harness for Stop-hook evolution flow

Dev-only slash command that auto-cycles the full E2E path for the
Stop-hook evolution AskUserQuestion feature. Per scenario: backup
state and hooks, seed test party, swap hooks.json to worktree paths,
spawn a fresh tmux pane with isolated CLAUDE_CONFIG_DIR, launch
Claude Code, capture the AskUserQuestion render, send-keys the
scenario's expected answer, 3-layer verify (UI regex, tokenmon evolve
tool call, state diff), restore backup. No-arg runs all 6 scenarios
sequentially; --scenario <name> runs one; --restore cleans up after
an aborted run; --dry-run lists scenarios without LLM cost.

Harness is excluded from the published plugin via the new files
allowlist in package.json.

- 6 scenarios covering branch, single-chain, batch, overflow, refuse
  persistence, and accept-clear-reprompt lifecycle
- backup.ts: dual-format hooks.json swap (baked absolute paths OR
  CLAUDE_PLUGIN_ROOT/DATA template vars), byte-level restore, gen-aware
  state/config paths; resolves hooks path via PLUGIN_ROOT walk-up
- tmux-driver.ts: pane spawn with isolated CLAUDE_CONFIG_DIR,
  capture-with-pattern-wait, numeric + text send-keys, graceful
  tmux-missing fallback
- verify.ts: 3-layer assertion from UI regex through tool call
  detection to state diff against expected_after
- cli/test-evolve.ts: SIGINT handler + try/finally for crash-safe
  restore (duplicate prompt preferred over silent loss)
- skills/test-evolve/SKILL.md: slash command entry delegating to the
  tsx CLI
- Tighten existing e2e test types (any to Partial<State>, Partial<Config>)
  and drop the unused execFileSync import

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(test-evolve): simplify harness to manual user-driven flow

Drop all tmux automation, Claude Code spawning, and UI/tool-level
asserts. Claude Code's Ink-based REPL does not accept tmux send-keys
submissions, which made the "spawn a child session and auto-answer
AskUserQuestion" path unreliable and expensive to debug. The simpler
and more useful shape is: backup, seed, swap hooks.json, let the
human trigger the prompt in their own live session, then verify state
and restore.

CLI subcommands are now:
  tokenmon test-evolve --list                list all scenarios
  tokenmon test-evolve --setup <scenario>    backup + seed + swap
  tokenmon test-evolve --verify              state diff vs expected
  tokenmon test-evolve --restore             byte-level restore

A tiny current.json pointer under .tokenmon/test-backup/ lets
--verify and --restore work without passing the scenario name again.

- cli/test-evolve.ts: 463 -> 156 lines, no tmux/spawn/waitForPattern
- test-evolve/verify.ts: 245 -> 115 lines, state-only assertions
- test-evolve/tmux-driver.ts: deleted
- skills/test-evolve/SKILL.md: rewritten for manual dispatch

Also pick up two earlier fixes needed to make the setup path actually
work end-to-end:
- backup.ts swapHooksJson: regex now accepts JSON-escaped `\"`
  surrounding baked absolute paths so hooks.json rewrites apply when
  the user runs with baked (post-install) hook paths
- backup.ts: drop the hardcoded `/home/minsiwon00/...` fallback and
  resolve the active hooks.json via PLUGIN_ROOT + a marketplace
  fallback, throwing when neither exists instead of writing to a
  non-existent path

Verified: typecheck passes; 1203/1203 tests pass; round-trip
--setup branch-eevee then --restore produces byte-identical state,
config, and hooks files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test-evolve): auto verify + auto restore after evolution event

The previous SKILL.md asked the user to manually invoke
/tkm:test-evolve --verify and /tkm:test-evolve --restore at the end
of each scenario. That contradicted the original design goal — the
cycle (seed → test → verify → restore) must always close itself so
the user's live state and hooks.json never stay mutated past the
test window.

Update the skill to orchestrate the full cycle across turns:

1. On /tkm:test-evolve <scenario>: run --setup, tell the user to
   send any message, then stop the turn.
2. On the next turn, after the Stop hook emits the block reason and
   the user picks an option (or refuses) via AskUserQuestion, run
   `tokenmon evolve <pokemon> <target>` for the chosen target(s).
3. Immediately after the evolve call(s) succeed, run --verify and
   then --restore, always, even if verify reports FAIL. Restore is
   unconditional so the user's real state/config/hooks.json come
   back to pre-test bytes.

--restore is still exposed as a manual escape hatch for the case
where the session is killed before the auto-restore step runs.
--verify is no longer a user-facing command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(stop): emit evolution block on first stop + drop status-line hint

Two adjustments so the evolution prompt is always visible through the
AskUserQuestion path:

1. Block detection moves BEFORE the first_stop/no_delta early return
   in stop.ts. The prior placement meant that if evolution_ready was
   already set when a new session started (e.g. after a cheat/test
   seed, or a resumed conversation where conditions had been met but
   the block had never fired), the very first Stop silently returned
   and the user had to send a second message before AskUserQuestion
   surfaced. Running block detection regardless of the lock result
   kind fixes this and keeps the existing `evolution_prompt_shown`
   guard so duplicate blocks are still prevented.

2. Drop the `evolution_ready` hint from the status line. Because the
   Stop hook now reliably produces an AskUserQuestion prompt on every
   qualifying stop, rendering the same "pokemon ready to evolve"
   notice in the status line was redundant noise — the prompt itself
   is the canonical surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(i18n): verbatim pokemon-voice question + overflow rule for 4+ branches

Three polish items surfaced during live testing of the evolution
AskUserQuestion flow:

1. Tone mismatch. Claude was paraphrasing the AskUserQuestion
   question text instead of using the pokemon-voice phrasing
   ("...어라!? {pokemon}의 상태가...?") that the status line had
   been using. Each locale's hook.evolution_candidate_line now
   carries the exact per-pokemon question string under a "use
   VERBATIM" label, and hook.evolution_block_reason instructs Claude
   to copy that string into AskUserQuestion.question without any
   rewording.

2. Overflow handling for branches with more than 3 targets (Eevee has
   8). AskUserQuestion caps at 4 options, so the previous "all
   targets + Refuse" instruction silently broke for Eevee-class
   pokemon. The new reason text spells out the rule: ≤3 targets show
   all + Refuse, 4+ targets show the first 3 + Refuse and list the
   remaining targets in the question body so the user can pick any of
   them via 'Other'.

3. Cross-gen name resolution in getPokemonName. Seed data (and any
   other path that surfaces a pokemon not native to the active
   generation's i18n) was falling back to the numeric ID, so the
   block reason rendered "133" instead of "이브이". Added a cross-gen
   lookup that searches each generation's game i18n before using the
   ID as the final fallback; the active generation is still preferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(i18n): validate Other-input targets before evolve, re-prompt on mismatch

Without an explicit rule, Claude could blindly feed a user's freeform
'Other' response into `tokenmon evolve`. A garbled or off-list name
then errored silently inside applyBranchEvolution (not in the
evolves_to list -> null return) with no useful feedback to the user.

Extend hook.evolution_block_reason in all four locale + voice combos
with an explicit handling rule:

- Button picks run `tokenmon evolve` directly.
- 'Refuse' (button or text: refuse/no/cancel/거부) skips.
- Free-text 'Other' must be validated against the target list
  (case-insensitive, English and localized names both accepted)
  before the command runs. On mismatch, reply with a short "I didn't
  recognize that" and re-invoke the same AskUserQuestion. Re-prompt
  caps at 2 iterations so a user who keeps typing garbage eventually
  lands on an implicit 'Refuse' instead of an infinite loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(evolve+test-evolve): cross-gen name lookup, resolve target, seed items

Three live-test fallouts:

1. Cross-gen reverse name lookup in pokemonIdByName. Previously
   searched only the active generation's i18n, so "이브이" in a
   gen4-active save returned undefined and the evolve CLI said
   "컬렉션에서 "이브이"을(를) 찾을 수 없다". Mirror the forward lookup
   from getPokemonName: try the active gen first, then fall back
   across installed gens' ko/en tables. Real-gameplay benefit too —
   any path that accepts user-typed names for pokemon from other
   gens now resolves.

2. cmdEvolve's targetArg was passed through to the branch.name
   string-equal comparison without being resolved through
   resolvePokemonArg. That forced Claude (or the user) to pass
   numeric IDs; a localized name like "샤미드" always fell through
   to "현재 ~의 진화 조건을 만족하는 경로가 없다" even when the
   target was actually eligible. Apply the same name→ID resolution
   we already use on pokemonArg.

3. The branch-eevee test scenario seeded `evolution_options` with
   all 8 Eeveelutions but never seeded the evolution conditions, so
   applyBranchEvolution's runtime check rejected every choice. Add
   `items: { water-stone, thunder-stone, fire-stone }` to the scenario
   seed (and an optional `items`/`current_region` pair on the
   Scenario type + writeSeed) so the chosen target actually evolves,
   and trim evolution_options to the 5 branches that are genuinely
   eligible (stones + friendship) for clearer overflow testing.

All 1203 existing tests still green; harness CLI typechecks clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test-evolve): restore 8-branch Eevee + force same-turn completion in skill

1. branch-eevee now seeds the full 8-branch evolution_options again
   (134/135/136/196/197/470/471/700). Only 3 are eligible via the
   seeded stones + 2 via friendship, but all 8 belong to the real
   Eevee dex so limiting the seed to 5 was misleading — users
   expected the complete set in the AskUserQuestion prompt. The
   ineligible three (Leafeon/Glaceon/Sylveon) naturally end up in
   the question body's "Other forms" list because the 4-option cap
   rule only promotes eligible targets to buttons.

2. Rewrite skills/test-evolve/SKILL.md so the evolution-trigger turn
   is treated as one continuous cycle instead of a turn-N /
   turn-N+1 split. The previous wording let Claude stop after the
   user answered the AskUserQuestion without running `tokenmon
   evolve`, verify, or restore. The new instructions say explicitly
   that the entire chain (render question → run evolve → print
   summary → --verify → --restore → final report) must complete
   within the same turn without stopping early.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(evolve): cross-gen ensurePokemonInDB fallback for non-native source

When the active generation's pokemon DB does not contain the source
pokemon (e.g. Eevee #133 in a gen4-active save), checkEvolution,
getEligibleBranches, applyBranchEvolution and applySingleChainEvolution
all returned null/empty because `db.pokemon[baseId]` is undefined.
That left test-evolve --setup branch-eevee + Claude-driven evolve
failing with "현재 이브이의 진화 조건을 만족하는 경로가 없다" on every
scenario that used a cross-gen pokemon — and would also affect any
real-gameplay path where a user's party member came from another
generation via migration.

Fix: every access of the source pokemon's data now falls back to
ensurePokemonInDB, which transparently walks the other generations'
data and injects the missing pokemon into the active gen's cache.
applyBranchEvolution also applies the same fallback for the target
data so cross-gen branch targets resolve.

Also correct branch-eevee.json: the crawled data only contains the
three stone-based Eeveelutions (Vaporeon/Jolteon/Flareon), so the
scenario's evolution_options list is trimmed to match reality. The
later Eeveelutions (Espeon/Umbreon/Leafeon/Glaceon/Sylveon) are a
data-crawler backlog item, not an overflow-path regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(evolve): single-chain support in cmdEvolve + cross-gen target fallback

Live scenario testing surfaced two related failures:

1. cmdEvolve only dispatched through getEligibleBranches, so pokemon
   whose data.evolves_to is a string (or legacy line[stage+1]) — i.e.
   every non-branching pokemon — hit "no eligible" and returned
   without invoking executeEvolve. That defeated the Stop-hook
   AskUserQuestion flow for any single-chain pokemon: the user would
   see the prompt, click the target, and nothing would happen.

   Add a dedicated single-chain branch in cmdEvolve that routes
   through checkEvolution (no state → returns a validated
   EvolutionResult) and then executeEvolve, which already dispatches
   to applySingleChainEvolution under the hood. The branch runs
   ahead of the getEligibleBranches path so branching pokemon still
   use the original flow.

2. checkEvolution's string-evolves_to path only called
   ensurePokemonInDB for explicit cross-gen references like
   "gen1:25". Plain numeric IDs that happened to live only in another
   generation (e.g. Charmeleon #5 on a gen4-active save) fell through
   and returned null because targetData was undefined. Same issue on
   the legacy line[stage+1] path. Both paths now use the
   ensurePokemonInDB fallback so cross-gen targets resolve.

Also seed the required evolution stones on the multi-3 and
overflow-5 scenarios so Pikachu (thunder-stone) and Eevee
(water/thunder/fire stones) can actually evolve when chosen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(stop): append test-harness verify+restore to block reason when active

The test-evolve skill only lives in Claude's context during the turn
that invokes /tkm:test-evolve (setup turn). On the follow-up turn
where the Stop hook emits the evolution block and Claude renders
AskUserQuestion, the skill content has usually dropped out of the
prompt window, so Claude hands control back to the user after the
evolve call instead of running the auto verify + auto restore tail
the skill required.

When stop.ts detects the harness marker file (
.tokenmon/test-backup/current.json), append an explicit
"[TEST HARNESS ACTIVE]" block to the reason that spells out the two
trailing commands with their absolute paths (the dev flow's own
PLUGIN_ROOT + bin/tsx-resolve.sh + src/cli/test-evolve.ts), plus a
reminder to print the final report. That keeps the cycle
self-closing whether or not the skill-turn history is still in
context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address review comments (P1 single-chain, P2 hooks cache lookup + achievements)

Three review findings from review-comments.md:

1. [P1] applySingleChainEvolution now falls back to ensurePokemonInDB
   for plain numeric cross-gen targets, not just the explicit
   `genN:id` crossGenRef syntax. Matches the fallback already in
   checkEvolution so every Stop-hook evolution that asks the user
   can actually resolve when the target species lives in another
   generation's dex. The legacy line[stage+1] branch picks up the
   same fallback for symmetry.

2. [P2] getInstalledHooksPath in src/test-evolve/backup.ts now
   searches ~/.claude/plugins/cache/tkm/tkm/<version>/hooks/hooks.json
   in addition to the worktree PLUGIN_ROOT and the marketplaces tree.
   That is where the release install actually lives — the prior
   helper threw before creating backups on any machine installed via
   the cache path, which was exactly the setup the new harness
   documents.

3. [P2] cmdAchievements now reads commonState in addition to
   state.achievements and merges entries from getCommonAchievementsDB
   so cross-generation achievements such as all_gen_badges stay in
   the listing. Before this patch the command only consulted the
   active-gen table so common achievements silently disappeared once
   a user unlocked them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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