Undertale In Extremis
TeX

Undertale In Extremis

计算机组成原理(Organização de Computadores)项目。类Undertale的RISC-V汇编RPG战斗模拟器。

大学项目(ORG 2026.1)。一个完全用RISC-V汇编编写的RPG战斗模拟器和蒙特卡洛引擎。

背景

我从零开始用纯RISC-V汇编构建了一个RPG战斗引擎。目标是编写三种不同的AI状态机,将它们放入一个无头模拟器中,运行10,000场自动对战,看看哪种策略能胜出。

战斗围绕严格的行动经济系统展开。MP恢复缓慢,但可以通过特定技能进行过载充能。

技能 消耗 效果
攻击 免费 投掷1d20。<10未命中,10+命中,20暴击造成双倍伤害。
防御 免费 格挡受到的伤害。高掷骰结果触发反击。
绝对意志 20 MP 绕过骰子,保证暴击。
灵魂吸取 免费 施法者承受1-12点反噬伤害,从敌人MP中吸取等量数值,并为自己获得4倍该数值。唯一能突破100 MP上限的方式。
最终处决 150 MP 800%伤害的核弹。若目标生命值高于50%则失败。
镜面护盾 30 MP 将下一次受到的攻击反射回攻击者。

参赛选手

我编写了三个机器人,每个都有完全不同的思维方式:

Flowey(混沌): 纯随机数生成器。他不做任何评估,只是随机选一个数字来决定下一步行动。

decision_random:
  li a0, 6
  call randomizer  # 选一个1到6之间的数字,这就是全部策略
  j decision_end

Chara(努力型): 她优化了一套连招:不断使用灵魂吸取直到MP达到150,将敌人生命值削减到50%以下,然后释放最终处决。在代码中这个策略也被命名为"智能",但实际并非如此。

decision_smart:
  # 我写了这个策略
  # 它的思路是使用灵魂吸取直到可以使用处决
  # 但为此我们还需要存活下来并将敌人生命值降到50%
  # 同时不能死亡
  ...
  li t6, 150        # 处决的消耗
  blt t4, t6, decision_smart_my_mana_is_low   # mp < 150?去刷MP
  bge a2, t6, decision_smart_enemy_hp_high    # 敌人生命值 > 50?去压制
  j decision_smart_i_can_kill                 # 否则,执行处决

decision_smart_my_mana_is_low:
  li a0, 4  # 灵魂吸取
decision_smart_enemy_hp_high:
  li a0, 2  # 绝对意志
decision_smart_i_can_kill:
  li a0, 5  # 最终处决

Toby(硬克制型): 专门为克制Chara而构建。他同时关注自己的生命值和敌人的MP条。如果自己生命值低于50且敌人MP达到150,他就举起镜面护盾让核弹反弹回去。除此之外,他混合使用攻击和灵魂吸取,条件满足时也能自己释放最终处决。

decision_troll_checks:
  li t6, 50
  ble t5, t6, decision_troll_check_enemy_mp  # 我的生命值够低需要担心吗?
  j decision_troll_not_execute
decision_troll_check_enemy_mp:
  li t6, 150
  bge a3, t6, decision_troll_prepare_against_execute  # 敌人MP接近150了吗?
  j decision_troll_not_execute
decision_troll_prepare_against_execute:
  li a0, 6  # 镜面护盾
  ret

基准测试结果

在终端中使用rars.jar(基于RISC-V的Java模拟器RARS的jar包)运行10,000场对战后,结果如下:

角色 胜率
Flowey ~52%
Toby ~29%
Chara ~17%

最愚蠢的AI赢得了绝大多数比赛。

Chara的17%胜率暴露了僵化贪婪连招的问题。为了达到150 MP,她必须承受灵魂吸取的反噬伤害。而Toby经常举起护盾,Chara的脚本迫使她不断消耗自己的生命值,直到在释放终极技能前把自己打死。

Toby的29%胜率来自成功引诱Chara。但面对Flowey时情况不同:护盾触发条件要求敌人MP接近150,而Flowey从不刻意积累MP。这个反制从未触发,所以Toby只能执行默认的攻击和灵魂吸取策略,这让他对混沌型对手没有任何优势。

Flowey以52%的胜率获胜,因为没有策略反而无法被持续反制。他从不为了设置大招而承受反噬伤害;他只是偶然打出高价值动作。事实证明,如果你的整个代码库依赖于预测敌人行为,那么面对一个毫无理由行动的敌人,你自动就会输。

这真的令人惊讶吗?

可能并不。随机智能体主导确定性智能体是策略模拟中一个被充分记录的结果。

我在学习小组中遇到的一个Citadel模拟产生了相同的模式:某些策略稳定地击败随机,某些被随机碾压,还有一些能击败特定对手但输给其他对手。石头剪刀布的动态关系确实存在,但随机仍然在大多数对局中站稳了脚跟。

区别在于,在更丰富的环境中(更多策略、更多决策变量、智能体更多利用随机的方式),结果往往更加分散。如果行动空间足够大,一个精心调整的确定性策略可以建立起可靠的领先优势。

在这里,可能并非如此。只有六种可能的行动,加上一个单次幸运暴击就能结束比赛的战斗系统,Chara的精心策划和Flowey的随机按键之间的差距并没有那么大。Chara的连招需要几个回合的铺垫,而随机数生成器有充足的机会在她达成目标前杀死她。行动空间可能太小、波动太大,以至于贪婪策略无法持续超越混沌。

换句话说:Flowey获胜与其说是一个发现,不如说是一个设计约束。一个更聪明的Chara需要一个更聪明的游戏来证明自己。

为什么这些数字不完全可靠

在从基准测试得出结论之前,有几个结构性偏差值得承认。

对战并非独立进行。 每场比赛双方都会随机分配一个策略,这意味着胜率是所有可能配对的总和,而不是干净的1v1统计数据。Flowey的52%包括Flowey对战另一个Flowey的比赛,而其中一方必须获胜。要真正知道Chara是否击败Toby,或者Flowey是否平等地击败所有人,你需要固定对战组合并分别运行每个配对。

回合顺序从未轮换。 玩家1总是先行动。在单次暴击就能结束比赛的系统中,先手是一个真正的优势。结果完全没有考虑这一点。

随机数生成器是一个自定义的xorshift,以系统时间为种子。 对于大学项目来说已经足够好,但它不是一个经过统计验证的生成器。如果6种可能行动的输出分布不均匀,Flowey的结果会直接受到影响,因为他的整个策略就是调用这个函数。

行动空间非常小。 六种可能的行动意味着任何策略都很难将自己与混沌区分开来。在更深的系统中,Chara的连招可能更难被打断,或者可能有恢复选项来减少她设置的代价。在这里,游戏恰好足够惩罚性,以至于她的反噬伤害经常在收益到来之前就结束了比赛。

Chara可能设计得不够好。 她的策略没有防御性的后备方案。如果连招条件不满足且她正在承受大量伤害,她仍然继续刷MP。一个更健壮的版本会在生命值低于某个阈值时切换到生存模式,这很可能会显著提高她的数据。

这些结果在方向上很有趣,但应被视为这个特定配置的快照,而不是关于策略与随机性的普遍性陈述。

未来研究

显而易见的下一步是完全脱离RARS。这个版本运行在基于Java的RISC-V模拟器上,这给模拟速度设置了硬性上限。第二个版本针对真实的编译型RISC-V,使用Linux系统调用而不是RARS ecalls,应该会快得多,一旦比赛数量开始攀升到数百万,这一点就非常重要。

除了性能之外,更有趣的方向是在设计方面:

每场比赛更多玩家。 目前总是1v1。加入第三个或第四个参与者会完全改变战略格局。突然间,反制策略必须考虑同时从两个方向受到攻击,纯随机性变得更难维持。

汇编中的机器学习。 想法是直接在RISC-V中实现一个最小的ML算法,使用矩阵运算让机器人根据比赛结果更新自己的权重。没有外部库,没有高级运行时,只有寄存器中的整数矩阵运算。这是否实用,或者只是一个有趣的痛苦实现过程,正是其吸引力的一部分。

更多策略和更深的引擎。 当前的行动空间太小,任何策略都无法有意义地超越随机。从《魔兽世界》中汲取更多直接灵感(冷却时间、资源权衡、定位效果、更多样化的技能互动)将为精心设计的策略提供实际空间来证明自己优于混沌。

基准测试基础设施已经就位。瓶颈在于游戏是否足够深入,使结果具有意义。

运行方式

要编译汇编代码并在本地运行引擎:

make simulate