OPEN SOURCE FIELD NOTE · WAR ANIMATION LAB
我们把本地桌面上的 war-animation-lab-oss 整理成了一个真正可访问、可运行、可复用的开源项目:war-animation-library。它不是“制图工具”,更像一个动画助手实验室:用数据描述战役,用通用渲染器播放历史,用 skill 把踩过的坑沉淀成下一次可调用的方法。

图 1|开源项目摘要卡片
01 / FIELD NOTE
这次想介绍的,不是某一张图,也不是单个战争地图动画。
我们把之前在本地桌面上折腾出来的动画助手项目 war-animation-lab-oss,整理成了一个可公开访问的开源项目。项目仓库叫 war-animation-library,地址是:
https://github.com/fuxiaoyan1/war-animation-library
线上演示地址是:
https://fuxiaoyan1.github.io/war-animation-library/
这是我们的首次开源。说“首次”,不是因为它已经多么成熟,而是因为它终于从“本地电脑上的一个实验文件夹”,变成了一个别人也能打开、阅读、运行、验证的东西。
它的中文名更像一个书架:战争动画藏书馆。
打开首页,看到的不是传统意义上的产品首页,而是一排排战役卡片:亚历山大、布匿战争、大秦统一、垓下之战、凯撒、十字军、蒙古帝国、拿破仑、对马海战、中途岛、德法战役、苏德战争、太平洋战争、抗美援朝、海湾战争……
这些战争被放进一个统一的互动动画框架里。每部动画都有播放、暂停、回放、时间轴拖拽;地图可以平移缩放;路线、单位、事件点、旁白、音效和故事面板一起工作。

图 2|线上演示首页截图
我的感觉是:它不像一个“成品展厅”,更像一个还带着木屑味的小工坊。桌上有战役数据,有地图投影,有单位图标,有音效,有 Playwright 测试,还有一个专门写给动画构建代理的 skill。东西还在长,但骨架已经立住了。
02 / 它到底是什么
如果只看页面,它像一个历史战争动画合集。
但如果打开仓库,会发现它真正想做的事情更底层:把战争动画拆成一套可复用的表达方法。
在 README 里,项目写得很直接:这是一个 data-driven interactive web animations for historical campaigns,也就是“数据驱动的历史战役互动 Web 动画”。仓库当前检查到的提交版本是 2c817ae,主分支为 main。
技术栈也很轻:React 19、Vite 7、TypeScript、d3-geo、topojson-client、world-atlas,再加上 Playwright 做浏览器烟测。它没有把战役动画写成一堆一次性的页面,而是把“战役事实”和“动画表现”拆开。
大体结构是这样:
也就是说,这个项目一开始就没有只追求“看上去能播放”。它在尝试回答一个更麻烦的问题:如果以后还要继续做第 16 部、第 30 部、第 100 部战争动画,哪些东西应该抽象出来?哪些坑必须写成规则?哪些效果不能只靠肉眼感觉?

图 3|手绘架构图:从史料到可播放战役
03 / 项目架构
这个项目最重要的架构决定,是把战役建模放在 JSX 渲染之前。
以 src/data 目录为例,仓库里有 15 个战役数据文件:alexanderConquests、punicWars、qinUnification、gaixiaAmbush、caesarWars、crusades、mongolEmpire、napoleonicWars、tsushimaBattle、midwayBattle、battleOfFrance、easternFront、pacificWar、koreanWar、gulfWar1991。
这些文件不是简单写几段文字,而是把战役拆成事件、地点、路线、前线、阶段、旁白、音效提示和地图焦点。这样做的好处是:动画不再是“写死的表演”,而是可以被时间轴驱动、被测试脚本检查、被后续 skill 复用。
通用渲染层的核心是 CampaignMapAnimation。它负责把战役数据转成可播放场景:
这里有一个小但很关键的设计:项目不是只有“动画”,还有“可操作的动画”。用户可以拖动时间轴,也可以点事件;地图不是一张死图,而是可以缩放、拖拽、双击复位。对于战争这种空间关系很强的内容,这一点很重要。

图 4|1940 德法战役开场截图
04 / 设计要求
这个项目里有一些很有意思的“硬约束”。它们不像口号,更像是从一次次返工里总结出来的工作规矩。
第一,每部战争动画默认控制在 5 分钟。
这不是说历史被压扁成 5 分钟,而是说动画作为一个可观看、可拖拽、可复盘的表达单元,不能无限拉长。长战争要压缩空档期,短战役要支持小时级时间轴,多战线战争则不能把不同战线各自乱压缩到失去同时性。
第二,战役数据要和渲染分离。
事件、坐标、路线、阶段、来源和旁白,尽量进入数据文件;地图投影、时间轴压缩、路线插值、控件行为进入 lib 和组件层。这样以后新增一场战役,不必从零写一个完全不同的播放器。
第三,视觉效果必须服务战役语义。
古代战役不能随手放现代爆炸;中途岛、太平洋这种海空战要有航母和飞机;朝鲜战争的飞机、坦克、航母、步兵也不能用后世装备随便替代。项目的 skill 里甚至写了很多这种“不要偷懒”的规则:单位图标要符合时代,海战路线要走海上,空袭路线要用 aircraft marker,战车、骑兵、舰船、航母都不能被挤成正方形小图标。
第四,动画要能被测试。
仓库里的 Playwright 测试不只是检查页面能不能打开。它会检查首页卡片、地图布局、当前事件是否落在地图核心区域、字幕是否遮挡地图、时间轴 rail 是否对齐、地图缩放和平移是否工作、单位图标是否符合预期、音频资产是否能访问。
这就让“好不好看”背后多了一层工程化的底线:至少不能明显挡住、跑偏、加载失败、交互失效。

图 5|德法战役中段时间轴截图
05 / 动画助手与 skill
这个项目最有意思的地方之一,是它不只开源了动画代码,也把“怎么继续做动画”的经验开源了。
仓库里有一个目录:
agents/skills/animation-assistant/
里面的 SKILL.md 就像一个给 AI 动画助手看的操作手册。它要求助手先读项目结构,再建模,再渲染,再验证。它把动画拆成 Event、Track、Viewport、NarrationCue、Source 等概念,并把大量踩坑经验写进去。
比如:
不要全局给 svg 写样式,否则图标可能被放大到离谱。 d3-geo 视口拟合要注意 MultiPoint 和 Polygon 的差异。 长战争需要压缩空白时间,不能让动画大半时间都停在无事发生的年份。 跨日期变更线的太平洋战场要用专门投影策略。 字幕不能盖住地图,也不能挡住鼠标滚轮。 地图交互必须支持滚轮、Shift 横向滚动、按钮缩放、拖拽和平移。 播放进度和用户点击事件,都应该触发对应音效。 新增战役时要检查背景音乐是否和已有动画重复。
这其实就是“动画助手”的核心:不是一次性让模型生成一个页面,而是把项目里反复验证过的约束沉淀成 skill。以后继续做新战役,助手不必重新踩同样的坑。

图 6|动画助手工作循环手绘图
06 / 工具链
从技术侧看,war-animation-library 走的是非常朴素的 Web 工程路线。
React 负责组件化表达;Vite 负责开发和构建;TypeScript 负责类型约束;d3-geo 和 topojson-client 负责地图投影与地理数据;world-atlas 提供基础世界地图;lucide-react 提供一部分界面图标;Playwright 负责浏览器级验证。
仓库的 package.json 里有三条关键命令:
我在当前环境里实际跑了一遍:npm install 成功,npm run build 成功,animation-assistant 的健康检查脚本也通过,确认 README 里要求的关键目录、脚本、data-testid 与 docs/sources 都在。
烟测方面,本轮运行了 14 个 Playwright 用例,其中 12 个通过,2 个失败。失败点都不是“页面打不开”,而是很具体的视觉回归:Caesar Wars 和 Pacific War 的当前事件点在某些状态下超出了测试设定的地图核心区域阈值。这个结果反而挺能说明项目的性格:它不是只要能演示就结束,而是会把“事件点有没有跑到画面边缘外”这种问题也拿出来拷问。
所以在文章里我不想把它写成“完美可用”。更准确的说法是:这是一个已经开源、可运行、可演示、可验证,同时还保留着实验室气味的项目。它有构建通过的工程底座,也有仍需修正的视觉回归。
07 / 线上演示
线上演示放在 GitHub Pages 上:
https://fuxiaoyan1.github.io/war-animation-library/
首页按“古代战争”和“现代战争”分架。这个分类有一个明确边界:拿破仑及其后的战争归入现代战争,更早的战争归入古代战争。这个边界写在 WarLibraryHome 里,也写进了动画助手 skill 的规则中。
进入具体战役之后,页面会切换到地图动画。比如垓下之战,它不是只画楚汉两军的箭头,而是强调“十面埋伏”的围合感;中途岛则出现航母、飞机、岛屿和海空路线;海湾战争更现代,装甲推进、空袭、阶段压缩和现代战场符号会更明显。

图 7|垓下之战截图

图 8|中途岛海空战截图

图 9|第一次海湾战争截图

图 10|对马海战截图
这里最有趣的,不是单张截图有多惊艳,而是它们来自同一个表达框架。古代、海战、空战、现代装甲战,当然需要不同的素材和语义,但底层都要回到同一组问题:时间如何推进?空间如何投影?单位如何移动?事件如何突出?用户如何回看?测试如何证明它没有跑偏?
08 / 开源边界
首次开源,最容易忽略的一件事是许可边界。
这个仓库把代码放在 MIT License 下,这是清楚的。但 README 和 NOTICE.md 也明确提醒:媒体资产并不自动 MIT。音频、图片、单位图标等素材来自混合来源,可能有 public domain、CC0、需要署名、非商业限制或尚未完全核验的来源。
所以比较稳妥的理解是:
代码可以按 MIT 使用。 媒体资产要看 NOTICE.md 和 docs/sources 下的来源说明。 如果要商业使用或再分发素材,不能只看仓库许可证,要逐项确认素材来源。
这也是项目现在比较诚实的地方:没有把“开源”说成一切都随便拿,而是把代码、演示资产、来源归属分开处理。
09 / 为什么这次开源有意义
很多小项目停在第一步:能跑,能看,能发给朋友。
war-animation-lab-oss 这次往前走了一步:它开始把“做出来”变成“可复用”。
这两者差别很大。
能做出一个战役动画,说明我们有一次实现能力。 能把 15 部战役动画放进同一个藏书馆,说明我们开始有系列化能力。 能把数据、渲染、音频、地图、交互、测试分层,说明我们开始有工程化能力。 能把动画助手 skill 一起开源,说明我们开始把经验变成方法。
这也是我喜欢这个项目的地方。它不装成一个巨大的平台,也不急着把自己包装成“历史 AI 影像生成系统”。它更像一个实验台:左边放代码,右边放素材,中间放一套越来越清晰的动画生产规则。
有些地方已经很扎实,比如首页、通用渲染器、时间轴、地图交互、来源文档、构建流程。
有些地方还在继续打磨,比如更细的视觉回归、动态导入减小 bundle、更多战役的图标与音效统一、媒体来源进一步核验、截图和测试报告更系统化。
但开源最好的状态,往往不是“我已经完美”,而是“我把现场打开给你看”。
10 / FIELD NOTE
可以这样开始:
git clone https://github.com/fuxiaoyan1/war-animation-library cd war-animation-library npm install npm run dev
本地开发页面通常会在 Vite 输出的 127.0.0.1 地址上打开。
如果要做生产构建和基础验证:
npm run build npm run test:smoke node agents/skills/animation-assistant/scripts/check-animation-project.mjs .
如果只是想看看效果,直接打开线上演示就可以:
https://fuxiaoyan1.github.io/war-animation-library/
如果你对 AI 辅助开发更感兴趣,可以从 agents/skills/animation-assistant/SKILL.md 看起。那里面不像宣传文案,更像一本“事故记录本”:哪些地方容易错,哪些视觉规则要坚持,哪些测试必须补上,哪些看似小的交互会影响真实体验。
11 / FINAL NOTE
这次开源,我更愿意把它看成一个小小的出门礼。
war-animation-library 不是最终答案。它更像一座还在施工的动画藏书馆:书架已经搭起来,第一批战役已经放进去,灯光还在调整,标签还在重写,有些书页边角还没裁齐。
但它已经能被打开。
能被打开,就意味着它可以被阅读、被质疑、被 fork、被改进、被继续实验。
对一个首次开源项目来说,这就够重要了。
项目地址: https://github.com/fuxiaoyan1/war-animation-library
线上演示: https://fuxiaoyan1.github.io/war-animation-library/
也欢迎把它当成一个轻松的战史动画小实验来玩:点开一张卡,拖一下时间轴,看一条路线从地图上慢慢走过去。也许下一场战役,就会从这个小实验室里继续长出来。