mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 08:15:53 +00:00
fix: 改用 Gen 3+ 标准方法生成 PID/IV/闪亮/性别
- 新增 generatePID() 生成 32 位 Personality Value - IV 改为 PID 位提取法(word1/word2 各取 3 个 5-bit),替换 LCRNG - Shiny 检测改为 PID XOR 方法,阈值 < 16(Gen 8+ 约 1/4096) - 性别阈值从 (rate/8)*256 改为 rate*32,消除浮点精度丢失 - 生成生物时使用 randomAbility() 替代 getDefaultAbility() - 解决 #14 Shiny 检测、#15 IV 生成、#16 性别阈值、#20 Ability 选择 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { getSpeciesData } from '../dex/species'
|
|||||||
import { determineGender } from './gender'
|
import { determineGender } from './gender'
|
||||||
import { levelFromXp } from '../dex/xpTable'
|
import { levelFromXp } from '../dex/xpTable'
|
||||||
import { gen, TO_DEX_STAT, getSpecies } from '../dex/pkmn'
|
import { gen, TO_DEX_STAT, getSpecies } from '../dex/pkmn'
|
||||||
import { getDefaultMoveset, getDefaultAbility } from '../dex/learnsets'
|
import { getDefaultMoveset, randomAbility } from '../dex/learnsets'
|
||||||
import { randomNature } from '../dex/nature'
|
import { randomNature } from '../dex/nature'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,14 +15,17 @@ export async function generateCreature(speciesId: SpeciesId, seed?: number): Pro
|
|||||||
const species = getSpeciesData(speciesId)
|
const species = getSpeciesData(speciesId)
|
||||||
const actualSeed = seed ?? Math.floor(Math.random() * 0xffffffff)
|
const actualSeed = seed ?? Math.floor(Math.random() * 0xffffffff)
|
||||||
|
|
||||||
// Generate IVs (0-31) using simple hash from seed
|
// Generate PID (32-bit personality value) from seed
|
||||||
const iv = generateIVs(actualSeed)
|
const pid = generatePID(actualSeed)
|
||||||
|
|
||||||
// Determine gender
|
// Generate IVs (0-31) extracted from PID (Gen 3+ style)
|
||||||
const gender = determineGender(species, actualSeed & 0xff)
|
const iv = generateIVsFromPID(pid)
|
||||||
|
|
||||||
// Determine shiny status
|
// Determine gender from PID's low 8 bits (Gen 3+ style)
|
||||||
const isShiny = Math.random() < species.shinyChance
|
const gender = determineGender(species, pid & 0xff)
|
||||||
|
|
||||||
|
// Determine shiny status from PID XOR (Gen 3+ style)
|
||||||
|
const isShiny = checkShiny(pid, actualSeed)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: randomUUID(),
|
id: randomUUID(),
|
||||||
@@ -35,7 +38,7 @@ export async function generateCreature(speciesId: SpeciesId, seed?: number): Pro
|
|||||||
ev: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 },
|
ev: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 },
|
||||||
iv,
|
iv,
|
||||||
moves: await getDefaultMoveset(speciesId, 1),
|
moves: await getDefaultMoveset(speciesId, 1),
|
||||||
ability: getDefaultAbility(speciesId),
|
ability: randomAbility(speciesId),
|
||||||
heldItem: null,
|
heldItem: null,
|
||||||
friendship: species.baseHappiness,
|
friendship: species.baseHappiness,
|
||||||
isShiny,
|
isShiny,
|
||||||
@@ -103,24 +106,52 @@ export function getActiveCreature(buddyData: { party?: (string | null)[]; active
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate IVs from a seed value. Each stat gets 0-31.
|
* Generate a 32-bit Personality Value (PID) from a seed.
|
||||||
|
* PID is the core identity value used for shiny check, gender, IVs, etc.
|
||||||
*/
|
*/
|
||||||
function generateIVs(seed: number): Record<StatName, number> {
|
function generatePID(seed: number): number {
|
||||||
let s = seed
|
let s = seed
|
||||||
const nextRand = () => {
|
const next = () => { s = ((s * 1103515245 + 12345) & 0x7fffffff) >>> 0; return s }
|
||||||
s = (s * 1103515245 + 12345) & 0x7fffffff
|
return ((next() & 0xffff) | ((next() & 0xffff) << 16)) >>> 0
|
||||||
return s
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Generate IVs from PID using Gen 3+ style extraction.
|
||||||
|
* HP IV = bits 0-4 of (pid >> 16) | (pid & 0xffff) is NOT used here;
|
||||||
|
* instead we use the more common method:
|
||||||
|
* word1 = pid (lower 16 bits), word2 = pid >> 16 (upper 16 bits)
|
||||||
|
* hp = word1 & 0x1f, atk = (word1 >> 5) & 0x1f, def = (word1 >> 10) & 0x1f
|
||||||
|
* spe = word2 & 0x1f, spa = (word2 >> 5) & 0x1f, spd = (word2 >> 10) & 0x1f
|
||||||
|
*/
|
||||||
|
function generateIVsFromPID(pid: number): Record<StatName, number> {
|
||||||
|
const word1 = pid & 0xffff
|
||||||
|
const word2 = (pid >>> 16) & 0xffff
|
||||||
return {
|
return {
|
||||||
hp: nextRand() % 32,
|
hp: word1 & 0x1f,
|
||||||
attack: nextRand() % 32,
|
attack: (word1 >>> 5) & 0x1f,
|
||||||
defense: nextRand() % 32,
|
defense: (word1 >>> 10) & 0x1f,
|
||||||
spAtk: nextRand() % 32,
|
speed: word2 & 0x1f,
|
||||||
spDef: nextRand() % 32,
|
spAtk: (word2 >>> 5) & 0x1f,
|
||||||
speed: nextRand() % 32,
|
spDef: (word2 >>> 10) & 0x1f,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check shiny status using PID XOR method (Gen 3+).
|
||||||
|
* Shiny if (pid_upper16 XOR pid_lower16 XOR trainerID XOR secretID) < threshold.
|
||||||
|
* Since we don't have trainer IDs, use the seed's high/low as proxy.
|
||||||
|
*/
|
||||||
|
function checkShiny(pid: number, seed: number): boolean {
|
||||||
|
const pidUpper = (pid >>> 16) & 0xffff
|
||||||
|
const pidLower = pid & 0xffff
|
||||||
|
const trainerId = seed & 0xffff
|
||||||
|
const secretId = (seed >>> 16) & 0xffff
|
||||||
|
const xorResult = pidUpper ^ pidLower ^ trainerId ^ secretId
|
||||||
|
// Standard threshold: 1 (1/65536 per encounter, ~1/8192 with both checks)
|
||||||
|
// Gen 8+: 16 (1/4096 base rate, 1/1024 with charm)
|
||||||
|
return xorResult < 16
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get total EV across all stats.
|
* Get total EV across all stats.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ import type { Gender, SpeciesData } from '../types'
|
|||||||
/**
|
/**
|
||||||
* Determine gender based on species gender ratio.
|
* Determine gender based on species gender ratio.
|
||||||
* genderRate: -1 = genderless, 0 = always male, 1-7 = female chance = genderRate/8, 8 = always female
|
* genderRate: -1 = genderless, 0 = always male, 1-7 = female chance = genderRate/8, 8 = always female
|
||||||
|
*
|
||||||
|
* Gen 3+ style: PID low byte (0-255) compared directly against genderRate * 32.
|
||||||
|
* If value < genderRate * 32 → female, otherwise male.
|
||||||
*/
|
*/
|
||||||
export function determineGender(speciesData: SpeciesData, seed: number): Gender {
|
export function determineGender(speciesData: SpeciesData, seed: number): Gender {
|
||||||
if (speciesData.genderRate === -1) return 'genderless'
|
if (speciesData.genderRate === -1) return 'genderless'
|
||||||
if (speciesData.genderRate === 0) return 'male'
|
if (speciesData.genderRate === 0) return 'male'
|
||||||
if (speciesData.genderRate === 8) return 'female'
|
if (speciesData.genderRate === 8) return 'female'
|
||||||
// Use seed value (0-255) to determine gender
|
// Direct comparison: genderRate maps 0-8 to threshold 0-255 in steps of 32
|
||||||
const threshold = (speciesData.genderRate / 8) * 256
|
const threshold = speciesData.genderRate * 32
|
||||||
return (seed % 256) < threshold ? 'female' : 'male'
|
return (seed % 256) < threshold ? 'female' : 'male'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user