那天排查一个人物素材的生成问题时,现象并不显眼。普通执行路径里,我指定的正式人物档案能够正常生效;一旦打开隔离模式,把任务拆成一景一景地处理,画面里的人却悄悄换回了默认真人照。
最容易让人误判的地方在于:命令本身没有报错,配置也确实已经解析过。日志里甚至能看到“参考素材已准备好”之类的正常状态。真正出问题的,是后面的隔离分支没有继续使用那份已经解析好的结果,而是重新拼了一个临时参数对象,再走了一遍参考素材解析。
这件事表面上是素材选错,实际上是参数优先级在不同执行路径里失去了同一套解释。配置只在入口处生效,不等于配置已经贯穿了整条执行链。
普通路径正常,隔离路径失效
当时的任务同时涉及人物档案、显式参考图和隔离处理。人物档案可以理解为一套已经整理好的参考素材配置:它不只告诉系统“用哪张图”,还包含不同姿态下应该如何选择主参考。显式参考图则是用户直接指定的素材,优先级应该更高。
在普通路径里,这个顺序是清楚的。系统先接收参数,再解析出最终参考素材,后面的生成步骤使用解析结果。因此,正式人物档案会被转换成对应的主参考,用户直接指定的参考图也能覆盖档案配置。
隔离模式的目标不同。它会把任务拆开,针对每一景重新做姿态匹配。这样做本身有合理性:不同景别、不同角度可能需要不同参考,不能简单把整批任务的结果原封不动地复用。但“重新匹配姿态”不应该等于“重新忘记配置”。
问题正是在这里发生的。隔离分支为了调用原有解析函数,构造了一个很小的临时参数对象。这个对象只把显式参考图传了过去,却没有把已经解析好的档案参数一起带上。解析函数看到的就像是一个没有档案配置的请求,于是按默认规则找回了 canonical 真人照。
如果不处理,这类错误会导致同一个任务在普通模式和隔离模式下产出不同人物。更麻烦的是,差异未必每次都容易被发现:画面可能仍然清晰、姿态也可能匹配,只有人物身份或外观一致性出了问题。等到成片交付后再追查,已经很难判断是素材问题、生成波动,还是隔离模式的分支逻辑造成的。
先把优先级写清楚
我一开始也差点沿着“隔离模式是不是不稳定”这条线去查。后来把问题缩小到参数解析,先不看生成结果,而是把参考素材的优先级写成一句明确规则:显式参考图最高;没有显式参考图时,使用人物档案;两者都没有时,才允许回到默认素材。
这一步看似只是整理表达,实际上改变了排查方式。只要某个分支不能回答“当前最终参考来自哪一级配置”,它就还没有完成解析。不能因为参数对象里有一个名为 reference 的字段,就认为优先级已经被保留。字段存在和语义存在,是两回事。
任何会重新组装参数的分支,都必须重新证明自己保留了完整的优先级,而不是只传递看起来最显眼的字段。
这里最容易误判的错误捷径,是只检查主流程的参数是否正确,然后默认隔离路径会复用同一个对象。主流程的断言会通过,普通任务也会正常;但隔离分支一旦自己创建了临时对象,就已经脱离了原先的参数上下文。
我选择先搜索所有重新构造参数对象的地方,是因为这比盯着最后一张错误图片更接近根因。图片是结果,临时对象才是信息丢失发生的位置。只要找到所有这样的分支,就能判断它们是在做必要的局部适配,还是无意中复制了一套不完整的解析逻辑。
表面问题是丢参数,真正问题是解析入口分裂
找到原因后,最直接的做法可能是给每个调用点各补一个字段。这样改起来很快,但我没有选择它。因为隔离处理有不止一个调用点,今天漏掉档案参数,明天就可能漏掉新的优先级字段。每个地方各自修补,等于把同一条规则继续分散在多个角落。
我判断应该增加一个统一的逐景解析入口,是因为隔离模式真正需要的不是一份更大的临时对象,而是“按当前景别解析参考”的完整语义。这个入口负责先做姿态匹配,再应用人物档案规则;如果用户明确给了参考图,则直接尊重显式选择。调用方只需要告诉它当前参数和当前景,不再自己拼装半套参数。
修复没有重写整条生成流程,也没有改变普通路径的行为。两个隔离调用点统一接入这个入口,原来的副锚处理和其他生成逻辑保持不动。这样做的理由很简单:本次事故的边界已经明确,应该修复信息丢失的位置,不顺手扩大改动范围。
这件事让我重新意识到,单一入口的价值不只是减少重复代码。它更像一份可以被反复调用的解释承诺:无论请求来自普通路径还是隔离路径,同一组参数都必须得到同一套优先级判断。真正难的不是把字段传过去,而是确保每个分支都使用同一种含义理解字段。
用失败测试证明问题真的存在
为了避免把“修复后通过”误认为“修复正确”,回归测试先验证修复前的失败行为。隔离模式配合正式人物档案时,测试检查逐景解析得到的主参考是否是档案指定的正式素材。未修复时,结果会落到默认真人照,这正好复现了现场问题。
第二组测试覆盖相反的边界:显式参考图必须高于人物档案。不能为了让档案生效,就把用户直接指定的素材也强行替换掉。这个测试很重要,因为修复者很容易采取一种粗糙方案:只要发现档案配置存在,就优先套档案。那样虽然能修好第一个案例,却会破坏更高优先级的显式输入。
好的回归测试不只证明新配置能生效,还要证明旧的优先级承诺没有被修复动作覆盖。
这两组测试分别锁住了“档案参数生效”和“显式参数覆盖”两个方向。它们不是为了增加测试数量,而是为了把参数解析的边界写成可执行的事实。之后如果有人再次修改隔离路径,只要改变了优先级,测试就会在离结果还很近的地方发出信号。
修复完成后,全量测试结果为 117 passed。这个数字本身不是质量的全部,但它至少说明新增边界没有破坏原有行为。更关键的是,测试不仅覆盖了修复后的绿色结果,也确认了修复前确实会红。只有这样,测试才不是装饰,而是对问题因果链的记录。
统一解析也要保留停止边界
排查过程中还发现另一条重新生成路径可能存在相似风险:在特定的免费重生场景里,系统会使用素材桶轮换机制。它和这次隔离逐景解析的触发条件、业务目的都不同,所以我没有把它一起改掉。
这不是忽略隐患,而是给修复划边界。相似的代码形状不代表相同的行为契约。如果贸然把所有参考素材选择都改成同一套逻辑,可能会破坏原本依赖素材桶轮换的场景。一个可靠的修复,应该说明哪些路径属于本次问题,哪些路径只是值得另开任务调查。
这里也有一个常见的错误捷径:看到同类代码就全部顺手统一,认为改得越多越彻底。实际上,范围越大,越难证明没有改变别的业务行为。最小修复不是少改几行,而是只改变已经被证据证明错误的那条路径。
我更愿意把这种克制看成工程判断的一部分。发现问题之后当然可以继续追求“全局一致”,但全局一致必须建立在每条路径的语义都已经确认之上。否则,修复一个静默降级,又制造一个不容易察觉的新变化,代价会更高。
下次我会怎么做
下次遇到“配置明明传了,结果却像没传”的问题,我会先按执行路径画出参数流向,而不是先怀疑生成结果。具体来说,我会先列出显式输入、档案配置和默认值的优先级,再搜索所有重新构造参数对象、重新调用解析函数或进入隔离分支的地方。
接着,我会问每个分支 3 个问题:它是否拿到了完整的参数语义?它是否仍然遵守同一套优先级?它是否在局部重解析后把已经确定的配置悄悄降级?只要其中一个问题答不上来,就不能把“主流程通过”当作整体通过。
实现上,我会优先把解析逻辑收拢到一个入口。入口内部保留完整的优先级,调用方只提供当前场景所需的上下文。这样新路径接入时,少复制一份逻辑,也少一个静默丢配置的机会。
验证上,我会固定做一对边界测试:较低层的档案配置在隔离路径中确实生效;更高层的显式参数仍然能够覆盖它。先让修复前的错误被测试捕捉,再让修复后的行为通过,最后跑全量测试确认没有旁路回归。
一个流程成熟的标志,不是它能在普通路径上跑通一次,而是它在分支、重试和隔离执行中仍然记得自己已经做过的判断。配置真正传下去,不是字段还在对象里,而是每一条路都没有重新解释错它。