为什么宝可梦的个体值是“31”,努力值是“255”?从内存与汇编解读其机制【内部数据解析】
なぜポケモンの個体値は「31」で努力値は「255」なのか?メモリとアセンブリから仕組みを読み解く【内部データ解析】
我忽然产生了兴趣,于是决定去看看内存和汇编。
- 个体值的最高值(V)为什么是“31”?
- 努力值单项能力值的上限为什么是“255”?
- 为什么不是整齐的数字,而是这种不上不下的数字?
其实,在这些数字背后,隐藏着自1996年发售的初代《宝可梦 红·绿》延续而来的Game Boy极致的硬件限制,以及开发团队(Game Freak)令人惊叹的位运算匠人技艺。
这次,我们将实际查看初代《皮卡丘版》的内存(WRAM),并剖析宝可梦能力值决定程序的机制。
1. 前置知识:比特就是“翻倍游戏的开关”
在进入正题之前,先整理一下作为本文全部基础的位宽与最大值的对应关系。
所谓比特(bit),就像电灯的开关。
它只能具有“OFF(0)”或“ON(1)”这两种状态。
在二进制中,当所有比特都为 1(全 1)时的最大值用 2^n - 1 表示。
・4-bit(2^4 - 1) ➔ 15 (第一代的个体值上限)
・5-bit(2^5 - 1) ➔ 31 (现代的个体值上限)
・8-bit(2^8 - 1 / 1 Byte) ➔ 255 (现代的努力值上限)
・16-bit(2^{16} - 1 / 2 Byte) ➔ 65535 (第一代的努力值上限)
我们日常使用的十进制是“1、10、100、1000”这样每进一位就乘以 10,而计算机的二进制则是“1、2、4、8、16、32、64、128……”这样价值每次翻倍的“翻倍游戏”世界。

那么,让我们依次把这些开关“全部设为 ON”并试着相加吧。
-
将4个开关(4位)全部设为ON
1 + 2 + 4 + 8 = 15(初代个体值的最大值) -
将5个开关(5位)全部设为ON
15 + 16 = 31(现代个体值的最大值) -
将8个开关(8位=1字节)全部设为ON
31 + 32 + 64 + 128 = 255(现代努力值的最大值) -
将16个开关(16位=2字节)全部设为ON
2^{16} - 1 = 65535(初代努力值的最大值)
这些「15」「31」「255」之类不上不下的数字,正是「将准备好的位(开关)全部设为ON时的理论最大值」本身。
2. 前时代的混沌:初代的努力值「65535」与全能力满值的世界
让我们把历史回溯到Game Boy时代吧。
当时的努力值可以将所有能力值累积到“16位(最大65535)”。


▲ 实际的皮卡丘版内存(0xD13C~0xD145)。努力值为“FF FF(65535)”,所有能力都已达到上限的样子
说到底,为什么努力值需要“16位(65535)”这么多呢?
现代的努力值会像“打倒皮卡丘后速度+2”这样,每次增加1~3点。但初代则完全不同,采用的是“打倒对手的‘种族值’会直接加到自己的努力值上”这一极其豪爽的设定。
例如,打倒一只种族值为“HP:80、攻击:82、防御:78、速度:80、特殊:80”的妙蛙花,自己的各项能力努力值就会一下子飙升约+80。
如果把努力值设计成1字节(8位=最大255),仅仅对战3~4场就会立刻达到上限并溢出。为了承载从1级到100级要持续战斗数千次的RPG成长曲线,2字节(16位=最大65535)这样广阔的容器是不可或缺的。
此外,初代的能力值计算公式中还隐藏着一个数学技巧。
\text{由努力值带来的能力值提升值} = \left\lfloor \frac{\sqrt{\text{努力值}}}{4} \right\rfloor \times \frac{\text{等级}}{100}
它是取所累积努力值的「平方根(根号 \sqrt{} )」来进行计算的。
试着计算一下16位的极限值「65535」的平方根,
\sqrt{65535} \approx 255.998\dots \longrightarrow \mathbf{255}
也就是说,这是一个“为了让击败的敌人的种族值累加,先让其累积到16位(65535),在计算状态时取平方根,从而漂亮地落到8位(255)”的、极其合理且优美的数学设计。
来看看ROM内部的汇编指令
让我们来看看初代宝可梦ROM内部的状态计算例程(CalcStat)的实现。
出处:GitHub pret/pokered(engine/pokemon/stat_calcs.asm)
.statExpLoop
xor a
ldh [hMultiplicand], a
ldh [hMultiplicand+1], a
inc b ; ルートの候補値を1増やす
ld a, b
cp $ff
jr z, .statExpDone
ldh [hMultiplicand+2], a
ldh [hMultiplier], a
call Multiply ; (現在の候補値)^2 を計算
ld a, [hld]
ld d, a
ldh a, [hProduct + 3]
sub d
ld a, [hli]
ld d, a
ldh a, [hProduct + 2]
sbc d ; (候補値)^2 が 努力値 を超えたか判定
jr c, .statExpLoop ; 超えるまでループして平方根を算出
.statExpDone
Game Boy的8位CPU(定制Z80)并不存在“平方根指令”。因此,开发团队一边将候选值平方(call Multiply)一边比较,用算法的蛮力来求平方根。
3. 【验证】窥探初代皮卡丘的原始内存——消失的“HP个体值”之谜
那么,个体值又是怎样的呢?
初代的个体值是4个开关的“4比特(0~15的16个阶段)”。
这又是为什么没有采用一个整齐的数字呢?
答案就在于Game Boy极限的内存打包方式。
计算机的基本单位“1字节(8比特)”,从正中间对半分开,就可以分割成“4比特 + 4比特(高4位/低4位)”两个房间。这个4比特的单位被称为“半字节(nibble)”。
在当时初代的宝可梦中,随身携带的一只宝可梦所分配到的数据区域仅仅只有44字节。
如果每一项能力值都各消耗1字节,那么光是个体值就会挤压宝贵的内存。因此开发团队选择了“把两项能力值的个体值紧紧塞进1字节里(半字节打包)”这一方案。
- 字节1:攻击(4bit) + 防御(4bit) = 1字节 (8bit)
- 字节2:速度(4bit) + 特殊(4bit) = 1字节 (8bit)
也就是说,所谓“为了在仅仅2字节(16位)的内存槽中尽可能多地塞入状态个体值的最小单位”,正是4位(0〜15)。
实际来看一下内存吧。

关注存储着皮卡丘个体值的地址 0xD146 和 0xD147。
- 0xD146 :
[ D C ] - 0xD147 :
[ 6 9 ]
十六进制的一位正好对应4位(0〜15)。试着把它分解成4个状态。
- D (十六进制) = 13 (攻击的个体值)
- C (十六进制) = 12 (防御的个体值)
- 6 (十六进制) = 6 (速度的个体值)
- 9 (十六进制) = 9 (特殊个体值)
……您注意到了吗?
「HP」的个体值,在内存中的任何地方都不存在。
Game Boy 卡带搭载的存档用 SRAM 和工作用 WRAM,只有区区几 KB 的极限世界。要保存 5 项能力的 4 位个体值需要 20 位(2.5 字节),但 0.5 字节(4 位)无论如何都不够。
4. 匠人技艺:初代开发团队是如何决定 HP 个体值的?
那么,HP 的个体值是如何计算的呢?
对初代 ROM 进行反汇编并分析代码后,当时程序员们的匠人技艺便浮现出来。
开发团队将“其他 4 项能力值的最低位(奇数还是偶数)”像拼图一样逐位拼接起来,从而当场炼成了本不存在的 HP 个体值。
- 攻击个体值 为奇数(最低位为 1)则 \longrightarrow +8 (bit 3)
- 防御个体值 为奇数(最低位为 1)则 \longrightarrow +4 (bit 2)
- 速度个体值 为奇数(最低位为 1)则 \longrightarrow +2 (bit 1)
- 若特殊个体值为奇数(最低位为1),则 \longrightarrow +1(bit 0)
我们来实际计算一下刚才的皮卡丘(DC 69)。
- 攻击个体值 : 13(奇数 / 二进制
1101)\longrightarrow bit 3 = 1(8点) - 防御个体值 : 12(偶数 / 二进制
1100)\longrightarrow bit 2 = 0(0点) - 速度个体值 : 6(偶数 / 二进制
0110)\longrightarrow bit 1 = 0(0点) - 特殊个体值 : 9(奇数 / 二进制
1001)\longrightarrow bit 0 = 1(1点)
\text{炼成的HP个体值} = 8 + 0 + 0 + 1 = \mathbf{9}
这个HP合成逻辑,我们也来看看ROM内部实际执行的原始指令序列。
出处:GitHub pret/pokered(engine/pokemon/stat_calcs.asm)
push bc
ld a, [hl] ; 攻撃個体値のバイトをロード
swap a
and $1 ; 最下位ビット(奇数なら1)を抽出
sla a
sla a
sla a ; 左へ3回ビットシフト(×8点)
ld b, a
ld a, [hli] ; 防御個体値のバイトをロード
and $1 ; 最下位ビットを抽出
sla a
sla a ; 左へ2回ビットシフト(×4点)
add b
ld b, a
ld a, [hl] ; 素早さ個体値のバイトをロード
swap a
and $1
sla a ; 左へ1回ビットシフト(×2点)
add b
ld b, a
ld a, [hl] ; 特殊個体値のバイトをロード
and $1 ; 最下位ビットを抽出(×1点)
add b ; 4つのビットを足し合わせてHP個体値(0〜15)が完成
pop bc
提取各能力值的最低位比特,再用sla a(Shift Left Arithmetic=算术左移)一边移位一边相加——这种匠人技艺就这样原样保留在代码中。
5. 第三世代(GBA)的革命:为什么进化成了「31」和「255」?
2002年,硬件完成了向「Game Boy Advance(GBA)」的世代更替。
CPU从8位进化到32位(ARM7TDMI),宝可梦的内部数据结构也经历了戏剧性的刷新。
在这里,让我们回到本文最大的疑问。
“为什么个体值是‘31’,而努力值是‘255’呢?”
我们将剖析成为决定性因素的、两个优美的工程学理由。
为什么个体值是“31(5位)”呢?──32位寄存器的极限打包
GBA的CPU是“32位”的。也就是说,CPU一次能处理的整数的标准寄存器大小以“32位(4字节)”为单位。
另一方面,第三世代的宝可梦总共有“6项能力值(HP、攻击、防御、特攻、特防、速度)”。
那么,若要“把一个32位整数箱子中均匀塞入6项能力值的个体值”,每项能力能分配多少位呢?
32 \text{ bit} \div 6 \text{ stats} = \mathbf{5.333\dots \text{ bit}}
作为整数能够均等分配的最大比特数,只有“5 比特”。
- 5 比特 \times 6 项能力 = 30 比特(正好能整齐地放进 32 比特中)
- 剩余的 2 比特 = 作为特性(Ability)标志或蛋标志被充分利用,毫不浪费
如果把一项能力设为 6 比特(0〜63),总计就会变成 36 比特,从 32 比特的盒子中溢出。反过来,如果保持 4 比特(0〜15)不变,就只会用到 24 比特,空间会剩余。
“在 32 比特 CPU 的箱子里,把 6 项个体值尽量塞到极限的最大公约数”──这正是 5 比特(2^5 - 1 = 31)。
为什么努力值是“255(8位)”?——维持初代“+63”的优化
初代的努力值会为每项能力消耗2字节(16位=65535)。如果算上6项能力,仅努力值就要占用12字节。
于是开发团队将每项能力的努力值压缩为“1字节(8位=最大255)”。
接下来才是Game Freak天才般的数理设计。
让我们看看GBA新的能力值计算公式。
\text{由努力值带来的能力值提升} = \left\lfloor \frac{\text{努力值}}{4} \right\rfloor \times \frac{\text{等级}}{100}
当等级为100时,努力值累积到最大255会怎样呢?
255 \div 4 = 63.75 \longrightarrow \mathbf{+63 \text{(最大提升值)}}
通过初代复杂的平方根计算(\sqrt{65535} \approx 255 \div 4 \approx 63)得到的最大提升值,也完全同样是“+63”。
开发团队“在分毫不差地维持初代‘努力值MAX时实际数值约提升63’这一培养平衡的前提下,完全废除了沉重的平方根例程,替换为仅将1字节的努力值右移2位(\div 4)的超高速处理”。
此外,合计值上还设置了“510”这一限制上限。
这是按 255 \times 2 = 510,也就是“恰好能让2项能力值专精(极振)到上限255的两倍数值”来设计的。
总结:约束催生的位运算艺术
我们不经意间严选、并在对战中使用的“31”和“255”这些数值。
那并不只是游戏平衡的数字,而是“四分之一世纪前,屏幕另一侧的开发者们在数KB的内存空间中绞尽脑汁,把一个个比特当作开关来战斗的那段历史本身”。
了解了这样的内部结构之后再去看游戏,平时游玩时看到的画面,是不是会呈现出截然不同的深度呢?
🎬 想通过视频直观理解的人
本文讲解的内容已使用自制动画制作成视频,并作为影像作品在 YouTube 上发布。
希望进一步加深理解的人,也请务必一并观看这段视频。
如果哪怕一点点激发了你的求知好奇心,也请务必点「Like」和「关注」。
今后我也会继续从底层视角讲解游戏内部数据、bug 的真相等内容。
下一回将为大家带来《为什么异色宝可梦的闪光概率是1/8192?异或(XOR)与边界值 bug 的数学解析》。
来源:zenn · 游戏开发社区 · zenn.dev