附录 A:环境与复现速查

卷一的复现手册:本机实测环境(Python 3.14 + numpy/pandas、Node 26 原生 TypeScript、无 matplotlib、无 tsc/npm)、bastion 完整目录结构、全部实跑命令、跨语言逐位一致的三条硬规则、以及一份可一键复现的校验脚本。遇到「书里的数字对不上」时先看这一页。

这一页解决一个问题:书里的代码怎么在我自己的机器上跑起来,跑出来的数字为什么可能和书上不完全一样。

一、本机实测环境

这套书的环境前提是实测过的,不是「一般来说」:

项实测结果说明
Python3.14.6S 层仿真
numpy2.5.1数组与随机数
pandas3.0.3CSV 与数据表
matplotlib未安装所以图表走纯 Python SVG
scipy未安装拟合用 numpy 手写最小二乘
Node26.5.0P 层原型
tsc / tsx / vitest均未安装所以不用它们

一条结论:这套书不需要安装任何东西。 Python 有 numpy 和 pandas 就够了,Node 直接跑 TypeScript。没有 package.json,没有 npm install,没有 tsconfig.json。

先确认你机器上的前提:

# file: check_env.sh
# tier: S
# verify: sh check_env.sh
python3 -c "import numpy, pandas; print('numpy', numpy.__version__, 'pandas', pandas.__version__)"
node -v

期望输出类似:

numpy 2.5.1 pandas 3.0.3
v26.5.0

只要 Python 能 import numpy, pandas、Node 版本 ≥ 26,这套书的所有代码都能跑。

二、bastion 目录结构

bastion/
  config/
    resources.json      # 资源定义与上限
    buildings.json      # 建筑等级、产出曲线、升级成本
    units.json          # 兵种属性与克制环
    battle.json         # 战斗规则常量
    season.json         # 赛季参数(卷二使用)
    pricing.json        # 付费与定价(卷二使用)
  sim/
    bastion_core.py     # 仿真核心:产出、成本、结算、战斗
    svg_line.py         # 零依赖 SVG 折线图
    ch01_*.py ... ch15_*.py   # 各章仿真脚本
  proto/
    src/
      core.ts           # 原型核心:与 bastion_core.py 逐位一致
      report.ts         # 打印对照报告
      ch01_*.ts ... ch15_*.ts
    test/
      bastion.test.ts   # 核心断言
      ch01.test.ts ... ch15.test.ts
  out/                  # 仿真产物:JSON / CSV / SVG

config/ 是唯一真源,sim/ 与 proto/ 都只读它。

三、全部实跑命令

S 层(Python 仿真)

# file: run_sim.sh
# tier: S
# verify: sh run_sim.sh
set -e
cd "$(dirname "$0")"

# 1) 仿真核心:产出、结算、升级成本、一场战斗
python3 sim/bastion_core.py

# 2) 零依赖 SVG 折线图
python3 sim/svg_line.py

# 3) 各章仿真(按需)
python3 sim/ch12_loop_sim.py --config config/ --days 180

P 层(TypeScript 原型)

# file: run_proto.sh
# tier: P
# verify: sh run_proto.sh
set -e
cd "$(dirname "$0")"

# 1) 直接执行一个 .ts 文件
node proto/src/report.ts

# 2) 跑断言(注意:必须用 glob,目录形式不发现 .ts)
node --test 'proto/**/*.test.ts'

一个容易踩的坑:node --test proto/(传目录)不会发现 .ts 文件,会报 MODULE_NOT_FOUND。必须用 glob:node --test 'proto/**/*.test.ts',或者显式指定文件 node --test proto/test/ch01.test.ts。这是实测结论。

一键校验

把两侧的对照报告都跑出来,逐位比对:

# file: sim/verify_two_consumers.py
# tier: S
# verify: python3 sim/verify_two_consumers.py
"""验证「一份配置两处消费」:Python 与 TypeScript 读同一份 config,结果必须逐位一致。"""

from __future__ import annotations

import json
import os
import subprocess
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
BASTION = os.path.dirname(HERE)
sys.path.insert(0, HERE)

import bastion_core as core  # noqa: E402


def ts_report() -> dict:
    out = subprocess.run(
        ["node", os.path.join(BASTION, "proto", "src", "report.ts")],
        capture_output=True, text=True, check=True,
    )
    return json.loads(out.stdout)


def main() -> int:
    py = core.battle_report()
    ts = ts_report()
    if py == ts:
        print("OK 两侧逐位一致")
        print(json.dumps(py["productionPerHour"], ensure_ascii=False))
        print("battle:", py["battle"]["winner"], "rounds=", py["battle"]["rounds"])
        return 0
    print("FAIL 两侧不一致")
    for k in py:
        if py[k] != ts.get(k):
            print(f"  diff {k}: py={py[k]!r} ts={ts.get(k)!r}")
    return 1


if __name__ == "__main__":
    raise SystemExit(main())

本机实跑输出:

OK 两侧逐位一致
{"wood": 132, "stone": 110, "iron": 27, "grain": 199}
battle: a rounds= 1

四、跨语言逐位一致的三条硬规则

这是这套工程的地基。违反任意一条,「一份配置两处消费」就会变成「一份配置两个答案」。

规则一:配置里不写浮点系数,写整数比或整数百分比。

{ "outputBase": 60, "outputGrowthNum": 122, "outputGrowthDen": 100 }

不要写 "outputGrowth": 1.22。原因是浮点幂运算在不同运行时末位可能不一致,取整后就会差 1。

规则二:幂运算在 TypeScript 侧必须用 BigInt。

122^9 已经超过 2^53,用普通 number 会丢精度。Python 侧用原生大整数就没这个问题。

// snippet: proto/src/core.ts 中的整数幂实现
function ipow(base: number, exp: number): bigint {
  let acc = 1n;
  const b = BigInt(base);
  for (let i = 0; i < exp; i++) acc *= b;
  return acc;
}

规则三:除法一律整数地板除,上取整用整数公式。

  • 地板除:Python a // b,TypeScript Math.floor(a / b)(且 a、b 为整数)。
  • 上取整:(a + b - 1) // b,不要用浮点 ceil。
# snippet: sim/bastion_core.py 中的整数上取整
def alive_count(hp_pool: int, hp: int) -> int:
    if hp_pool <= 0:
        return 0
    return (hp_pool + hp - 1) // hp   # 整数上取整

五、数字对不上怎么办

先按这个顺序排查:

  1. 配置被改过吗? 这正是「一份配置两处消费」该有的行为。改 outputGrowthNum 从 122 到 125,5 级伐木场产出会从 132 变到另一个值,书上写的是 122 时的结果。
  2. 环境版本差得多吗? numpy/pandas 的大版本变化可能影响随机数序列。涉及随机的地方,本套都用自己实现的确定性 PRNG(如 mulberry32),不吃运行时随机数实现。
  3. 是不是「同机相对量级」的说明? 正文写「同机相对量级,绝对数值不可移植」的地方(比如结算耗时、吞吐),绝对值本来就依赖机器,只有相对关系可移植。
  4. 还不对,那就是书错了。 请告诉我具体的节号和数字。

六、两条纪律

复现这件事上有两条纪律,这套书从头守到尾:

  • 不编造竞品后台数字。真实竞品只作「公开可观察机制」的引用并标注观察时点,需要进后台才能拿到的数字一律不写,这类内容标为 X 层。
  • 所有数字来自本机实跑,正文不写「提升 N 倍」式的绝对话,只写「同机同输入下 A 比 B 少 X 个结算轮次」这类可复核的比较。

配置表的字段字典与完整 schema 见附录 B 。回到目录页 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「slg」更多文章

  1. 12.3 留存与付费的联合分析
  2. 12.2 流失预警与召回
  3. 附录 C:全书公式、参数与数据集索引