Rule System
生成式内容
引擎架构
万利体育AI已经可以实时生成内容以后,为什么游戏引擎仍然必须保留大量传统规则系统?
万利体育AI工程笔记 · 引擎架构 · 约 6 分钟阅读
作为体育游戏的引擎工程师,我们几乎每天都要回答同一个问题:万利体育AI已经能够根据比赛情境实时生成解说、生成球员的战术选择,甚至生成一段全新的进攻套路,那为什么底层引擎里那一堆写了很多年的规则系统——越位判断、犯规判定、出界规则、比分计算——一行都没有删掉?
答案很朴素:生成式模型输出的是"可能性",而规则系统负责把这些可能性过滤成"合法性"。举一个具体例子。假设万利体育AI在一场对局里,根据当前的Game State(球权归属、剩余进攻时间、球员站位、体力值)生成了一次"背身单打后分球给外线队友"的战术建议。这段内容本身是模型基于历史数据和当前局面推理出来的一种可能路径,读起来完全合理,但它并不知道,也不应该知道,引擎里对"传球"这一动作的物理约束:球必须经过合法的传递轨迹,接球者的站位是否越界,防守方是否已经形成合法包夹从而触发抢断判定。这些判断不是"创意"问题,是"合法性"问题,必须由确定性的规则代码来把关,而不是交给一个概率模型去猜。
我们内部把这套分工叫做 Rule + Generation:生成层负责"想出什么内容有趣、什么内容符合球员风格与当前比赛节奏",规则层负责"判断这个内容能不能发生、发生之后比分和球权该怎么变"。如果反过来,让生成模型直接输出比分变化或者直接判定犯规,会出现两类典型故障。第一类是数值漂移——模型在长局对战里逐渐生成出不符合物理规律的动作序列,比如球员在没有触球权的情况下"生成"了一次射门动作。第二类是状态不同步——生成内容与Game State记录的实际数值出现分歧,导致界面显示的比分和引擎内部结算的比分对不上,这类问题极难复现和排查,因为它不是崩溃,而是"看起来正常但数字是错的"。
工程经验:凡是涉及比分、球权、犯规判定这类会直接改变比赛结果的字段,万利体育AI坚持"规则系统一票否决"——生成层只提供候选内容,任何生成结果都不允许绕过规则校验直接写入Game State。
另一个更容易踩坑的例子来自足球项目。万利体育AI在生成一次"直塞球撕开防线"的进攻内容时,需要同时考虑接球队员在传球出脚瞬间是否处于越位位置——这个判断依赖的是"出脚那一帧"的Game State快照,而不是"接球那一帧"的位置。如果规则系统只用最新的实时坐标去做越位判断,就会出现生成内容和真实规则脱节的情况:一次原本合法的直塞被误判为越位,或者反过来,一次实际越位的直塞被生成模型"忽略"掉了。这类依赖具体历史帧的规则细节,恰恰是生成模型不会主动关心的,它只负责判断"这个内容好不好看",越位判断这种时间维度上的规则,必须继续留在确定性代码里维护,不能因为引入了生成能力就顺手简化掉。
这套分工对测试流程也有直接影响。因为生成层带有随机性,同一份Game State在两次请求里可能生成出不同的候选内容,这意味着传统"给定输入、比对固定输出"的回归测试方法不再适用于生成层本身。万利体育AI的做法是把测试重心放在规则层:只要规则系统对任意候选内容的校验结果是确定且可复现的,就可以单独对规则层做完整的回归覆盖;而生成层则通过记录每一次"候选内容—校验结果"的完整日志来做事后分析,而不是指望生成内容本身每次都长得一样。
所以万利体育AI真正落地的方式,是让生成模型输出的是"意图"而不是"结果":它生成的是一次传球意图、一次战术选择、一段解说文本的候选内容,这些意图会先经过Rule System做合法性校验,通过后才会真正写入Game State并驱动画面。规则系统不会因为生成式内容的出现而变薄,反而因为生成内容天然带来更多不确定的边缘情况,规则层的边界条件必须写得更严格、覆盖面要更全,否则生成模型偶尔输出的极端情况就会变成线上事故,而这类事故通常很难在测试阶段被提前发现。
Design Goal
Game State
生成策略
万利体育AI读取完整Game State以后,为什么"知道所有数据"仍然不代表模型知道下一步应该生成什么?
万利体育AI工程笔记 · 生成策略 · 约 6 分钟阅读
很多人对万利体育AI有一个直觉误解:只要把完整的Game State喂给模型——球员坐标、体力、比分、剩余时间、历史动作序列——它就应该"知道"接下来该生成什么内容。这个直觉在工程上并不成立,原因是数据和目标是两件不同的事。Game State回答的是"现在是什么情况",而生成下一步内容需要回答的是"接下来应该往哪个方向走",后者需要一个模型本身不具备、必须由设计者显式给出的东西,我们内部把它叫做 Design Goal。
举个例子会更清楚。假设某一局比赛进行到读秒阶段,双方比分接近,Game State里所有字段都是完整的:谁控球、还剩多少体力、上一次进攻是什么套路。如果没有Design Goal,模型面对这份数据可以生成出很多个"合理"的下一步——可以生成一次保守的控球拖延,可以生成一次冒险的快攻,也可以生成一段偏向某个球员个人发挥的镜头。这些选项在数据层面都站得住脚,模型不会因为"不知道数据"而卡住,它会因为"不知道该往哪个方向优化"而生成出彼此矛盾、忽快忽慢、缺乏整体节奏感的内容。数据完整性解决的是"编造"问题——防止模型凭空生成不存在的球员或不存在的比分;但数据完整性解决不了"方向感"问题,方向感必须来自额外注入的设计目标。
工程经验:数据完整性和目标明确性是两条互相独立的检查项。万利体育AI在生成前会同时确认两件事——Game State是否完整、Design Goal是否已显式注入——任何一项缺失都会导致生成内容"技术上正确但体验上跑偏"。
这个问题在万利生涯AI和快速对战两种模式的对比里体现得更明显。同样一份Game State——比分接近、读秒阶段、球员体力接近耗尽——在生涯剧情模式里,Design Goal可能要求生成内容向某个角色的成长线索靠拢,比如安排一次能体现角色心态转变的关键球;而在快速对战模式里,同一份Game State对应的Design Goal可能只是单纯的"保持比赛悬念,不要提前分出胜负"。如果两种模式共用同一个Design Goal,要么生涯剧情会显得生硬突兀,要么快速对战会莫名其妙插入不相关的角色情节,这也是万利体育AI把Design Goal做成可按场景切换的独立参数、而不是写死在模型内部的原因。
判断一次生成结果是否达成了Design Goal,万利体育AI团队不会依赖模型自己打分,而是把生成内容和对应的Design Goal一起交给内部复盘流程:策划会对照当初设定的节奏目标、难度目标,逐条检查生成出的具体内容是否偏离,偏离的样本会被整理成反例,用来调整下一轮生成时的目标描述方式或约束条件。这个过程更接近人工评审而不是自动化打分,因为"是否好玩"本身很难被压缩成一个单一的数值指标去自动衡量,也很难脱离具体场景单独判断对错。
在实际工程里,Design Goal通常被拆成几个可以量化、或至少可以相对比较的信号:节奏目标(这段比赛需要偏紧张还是偏舒缓)、难度曲线目标(对手在这个阶段应该展现出更强还是更保守的策略)、以及叙事目标(如果这一局绑定了某种生涯剧情,生成内容需要向那个剧情的走向靠拢)。这些目标不是从Game State里"读"出来的,是策划和工程师作为显式参数注入生成流程的,模型的职责是"在给定目标下,从当前Game State出发生成一条尽量自然的路径",而不是"自己去发明一个目标"。
这也是为什么单纯扩大模型能读取的数据范围——比如把整个赛季的历史数据都塞进上下文——并不会自动让生成内容变得更好玩。数据量增加带来的更多是"更准确地描述现状",而不是"更清楚该往哪走"。万利体育AI在这一层刻意保留了一个独立的目标注入接口,把"这一局应该呈现什么样的体验"当作和Game State同等重要的输入,而不是指望模型从海量数据里自己摸索出设计意图。
Executable Generation
玩法设计
校验流程
万利体育AI生成新玩法时,为什么开发者最需要防止的不是内容重复,而是AI生成一个游戏根本执行不了的想法?
万利体育AI工程笔记 · 校验流程 · 约 7 分钟阅读
在讨论生成式玩法的时候,外部最常问的问题是"会不会生成很多重复内容",但从引擎工程的角度看,重复从来不是最危险的问题——重复的内容最多是显得无聊,无聊可以靠调整采样策略、扩大候选池慢慢缓解。真正危险的是另一件事:万利体育AI生成出一个描述上很有创意、但引擎里根本没有对应变量和触发器去执行它的"玩法想法"。这类内容不是无聊,是会直接导致功能失败甚至运行异常,而且往往要等到接入运行时才会暴露。
我们把这个问题叫做 Executable Generation:一个生成出来的玩法建议,能不能被落实为可执行的代码路径,取决于它能不能被拆解成引擎已知的变量和触发条件,而不取决于它读起来是否新颖。举个具体例子,假设生成模型输出了这样一条"新玩法":当球员连续多次精彩传球后,触发一种"团队士气"状态,让队友的动作生成变得更具冒险性。这句话作为文案完全成立,但要变成可执行内容,至少需要回答三个工程问题。第一,"连续精彩传球"要映射到哪个已有的统计变量,还是需要新建一个从未定义过的计数器。第二,"团队士气"这个状态要挂到Game State的哪个字段上,它的取值范围、衰减规则、和其他状态的互斥关系是什么。第三,"动作生成更具冒险性"要对应到动作生成模块里哪一个可调参数,如果这个参数根本不存在,这条生成内容就只是一句好看的话,没有任何执行路径。
工程经验:万利体育AI给新玩法生成设置的第一道门槛不是"是否有趣",而是"能否映射到已注册的变量、状态和触发器"。无法映射的创意,无论描述多吸引人,都会在校验阶段被拦下,不会进入实现排期。
为了在源头减少这类不可执行的生成内容,万利体育AI维护着一份内部的"能力清单":把引擎里已经注册的事件触发器、可读写的Game State字段、动作生成模块开放的可调参数,整理成生成模型在产出建议之前就能查询的结构化列表。生成流程会先参考这份清单再输出候选玩法,而不是完全自由地凭空构思,这样能在生成阶段就过滤掉一大批"引擎里根本没有对应实现"的想法,减少校验阶段被拦截的比例,也减少团队后续无谓的返工。
即便有能力清单,仍然会出现"部分可执行"的候选——比如触发条件能映射到已有变量,但效果参数只覆盖了描述的一部分。对这类情况,万利体育AI不会直接整体丢弃,而是走一条简化流程:保留能映射的部分,把无法执行的效果降级或省略,生成一个功能上更保守、但确实能跑起来的版本,再交给团队判断是否值得为剩余部分单独开发新的触发器或参数。这比"要么完全接受、要么整体作废"的二元处理方式更实用,也让能力清单本身能随着这类反例逐步扩充完善。
如果没有专门的校验层,这类"听起来对、落不了地"的生成内容会在开发后期造成大量返工——因为它们通常要等到真正接入运行时才会暴露出"根本没有对应实现"的问题,而不是在生成阶段就能被拦截。所以万利体育AI在生成新玩法建议时,会强制要求每一条候选内容同时输出一份对应的"执行映射":具体触发条件对应哪个已注册的事件,状态变化对应哪个已声明的字段,效果对应哪个已存在的参数接口。任何一项映射不到已有系统的候选内容,会在校验阶段直接被标记为不可执行。
这套流程的代价是生成的创意上限会被引擎现有能力约束住——模型不能天马行空地发明引擎完全没有的机制。但换来的好处是,每一条通过校验的生成内容,工程团队都能确认它是可以被实现、被测试、被上线的,而不是等到接入阶段才发现某个听起来很酷的玩法其实完全无法执行。