feat: Phase 1 — 数据模型升级 Creature v2 + PCBox/Bag

- 新增 MoveSlot, PCBox, Bag, ItemId 类型
- Creature 扩展 nature/moves/ability/heldItem/pokeball 字段
- BuddyData 升级 v2: 新增 boxes, bag, battlesWon/battlesLost
- 新建 data/learnsets.ts: getDefaultMoveset/getDefaultAbility/getNewLearnableMoves
- storage.ts v1→v2 迁移: 回填 nature/moves/ability,新增 PCBox/Bag
- 新增 PCBox 操作: deposit/withdraw/move/rename/findLocation/release
- 新增 Bag 操作: add/remove/getCount
- generateCreature/loadBuddyData/hatchEgg 改为 async (Dex.learnsets.get 异步)
- 修复 PokedexView: activeCreatureId → party[0]
- 更新测试文件: async/await + v2 BuddyData fixtures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-22 00:20:08 +08:00
parent 96f3e1b309
commit 12cbb7c4c7
16 changed files with 496 additions and 216 deletions

View File

@@ -4,8 +4,8 @@ import { generateCreature, calculateStats, getCreatureName, getTotalEV, recalcul
import { getSpeciesData } from '../data/species'
describe('generateCreature', () => {
test('creates a creature with correct defaults', () => {
const c = generateCreature('bulbasaur', 42)
test('creates a creature with correct defaults', async () => {
const c = await generateCreature('bulbasaur', 42)
expect(c.speciesId).toBe('bulbasaur')
expect(c.level).toBe(1)
expect(c.xp).toBe(0)
@@ -13,23 +13,23 @@ describe('generateCreature', () => {
expect(c.friendship).toBe(getSpeciesData('bulbasaur').baseHappiness)
expect(c.isShiny).toBeDefined()
expect(c.id).toBeTruthy()
expect(Object.values(c.iv).every((v) => v >= 0 && v <= 31)).toBe(true)
expect(Object.values(c.ev).every((v) => v === 0)).toBe(true)
expect(Object.values(c.iv).every((v: number) => v >= 0 && v <= 31)).toBe(true)
expect(Object.values(c.ev).every((v: number) => v === 0)).toBe(true)
})
test('deterministic IV generation from seed', () => {
const c1 = generateCreature('charmander', 12345)
const c2 = generateCreature('charmander', 12345)
test('deterministic IV generation from seed', async () => {
const c1 = await generateCreature('charmander', 12345)
const c2 = await generateCreature('charmander', 12345)
expect(c1.iv).toEqual(c2.iv)
})
test('different seeds produce different IVs', () => {
const c1 = generateCreature('squirtle', 100)
const c2 = generateCreature('squirtle', 200)
test('different seeds produce different IVs', async () => {
const c1 = await generateCreature('squirtle', 100)
const c2 = await generateCreature('squirtle', 200)
expect(c1.iv).not.toEqual(c2.iv)
})
test('all MVP species can be generated', () => {
test('all MVP species can be generated', async () => {
const species: SpeciesId[] = [
'bulbasaur', 'ivysaur', 'venusaur',
'charmander', 'charmeleon', 'charizard',
@@ -37,15 +37,15 @@ describe('generateCreature', () => {
'pikachu',
]
for (const s of species) {
const c = generateCreature(s)
const c = await generateCreature(s)
expect(c.speciesId).toBe(s)
}
})
})
describe('calculateStats', () => {
test('level 1 stats are reasonable', () => {
const c = generateCreature('bulbasaur', 0)
test('level 1 stats are reasonable', async () => {
const c = await generateCreature('bulbasaur', 0)
const stats = calculateStats(c)
// HP at lv1: floor((2*45 + iv + floor(0/4)) * 1/100) + 1 + 10
// With any IV: floor((90 + iv) / 100) + 11 = 0 + 11 = 11
@@ -56,8 +56,8 @@ describe('calculateStats', () => {
expect(stats.attack).toBeLessThanOrEqual(6)
})
test('stats increase with level', () => {
const c1 = generateCreature('charmander', 0)
test('stats increase with level', async () => {
const c1 = await generateCreature('charmander', 0)
c1.level = 1
const stats1 = calculateStats(c1)
@@ -68,8 +68,8 @@ describe('calculateStats', () => {
expect(stats50.attack).toBeGreaterThan(stats1.attack)
})
test('EVs affect stats', () => {
const c = generateCreature('pikachu', 0)
test('EVs affect stats', async () => {
const c = await generateCreature('pikachu', 0)
const statsNoEV = calculateStats(c)
const cWithEV = { ...c, ev: { ...c.ev, attack: 252 } }
@@ -80,27 +80,27 @@ describe('calculateStats', () => {
})
describe('getCreatureName', () => {
test('returns species name when no nickname', () => {
const c = generateCreature('pikachu')
test('returns species name when no nickname', async () => {
const c = await generateCreature('pikachu')
c.nickname = undefined
expect(getCreatureName(c)).toBe('Pikachu')
})
test('returns nickname when set', () => {
const c = generateCreature('pikachu')
test('returns nickname when set', async () => {
const c = await generateCreature('pikachu')
c.nickname = 'Sparky'
expect(getCreatureName(c)).toBe('Sparky')
})
})
describe('getTotalEV', () => {
test('returns 0 for new creature', () => {
const c = generateCreature('bulbasaur')
test('returns 0 for new creature', async () => {
const c = await generateCreature('bulbasaur')
expect(getTotalEV(c)).toBe(0)
})
test('sums all EV values', () => {
const c = generateCreature('bulbasaur')
test('sums all EV values', async () => {
const c = await generateCreature('bulbasaur')
c.ev = { hp: 10, attack: 20, defense: 30, spAtk: 40, spDef: 50, speed: 60 }
expect(getTotalEV(c)).toBe(210)
})