Skip to content

feat: ai_engine 视频路线真实现落地(Refs #53 #35) #171

Description

@johnnyzhang-eng

背景

ai_engine#53 落了空骨架(ports 契约 + DerivationStrategy 分流 + 串联),该 Issue 验收标准第 4 条写明:

内部真实实现不在本 Issue —— 另开开发 Issue,迁 windup-pipeline,带测试。

本 Issue 即该开发 Issue。实现来自个人管线仓的实测通路(定妆母版 → 图生视频 → 抽帧 → 抠图 → 像素化 → 对齐 → 序列帧),本次迁入主仓并补齐测试。

main 上现状:ai_engine.gitkeep 外无任何实现;windup_common/models 为空;windup_framework/providers 只有三个 create_*_client 工厂,无可供上层依赖的抽象类型。

实现范围

backend/pyproject.toml 的 import-linter 分层契约自下而上切 4 个 PR,每个 PR 自身 CI 可绿、不依赖未合分支。

PR 内容 依赖 规模 用例
1 windup_common 角色卡 / 动作规格 / 生成路线枚举 2 文件 +78
2 windup_framework 三个 Provider Protocol + 抠图 + 视频/图像实现 8 文件 +792 +7
3 windup_ai_engine 叶子 抽帧 / 选帧 / 后处理 / 提示词 19 文件 +1550 +17
4 windup_ai_engine 契约层 ports / strategy / impl PR 1·2·3 35 文件 +2994 +9

PR 1·2·3 互不依赖,可并行评审。PR 4 stack 在三者之上,合并后 rebase。

实现内容

1. 共享 DTO(windup_common/models

  • CharacterCard — 角色身份(name / desc / palette / view / master_ref / version)
  • ActionSpec — 动作规格(action / fps / loop / poses / stylize / pixel_h / palette_size / facing)
  • ActionType — idle / walk / run / jump / attack / hit
  • GenRoute — video_i2v / per_frame(只列有实现的路线,理由见 Q4)

2. Provider 抽象与实现(windup_framework/providers

  • interfaces.pyImageProvider / VideoProvider / MatteProvider 三个 Protocol,零依赖,供上层按能力而非按厂商声明依赖
  • matte.pyOnnxU2NetMatteProvider:onnxruntime 直跑 u2netp。不用 rembg:其依赖链 pymatting → numba 0.53 / llvmlite 0.36 在 Python 3.12 无轮子;rembg 内核本身就是 u2netp 过 onnxruntime,alpha_matting=False 时不碰 pymatting
  • matte.pyGreenScreenMatteProvider:绿幕抠图,背景色由调用方显式给出。附带溢色抑制(绿幕反光在主体边缘留一圈绿,实测占主体像素 7.1%,送进图生 3D 会被烤进贴图)
  • sufy.py — 图生视频 / 文生图 provider

3. 出帧工具箱(windup_ai_engine slicing / postprocess / prompt / master_prep)

零 windup 依赖,只用 PIL + numpy,可独立测试。

  • slicing/extract 解码;slicing/loop 循环类动作抽单步态周期;slicing/oneshot 一次性动作裁区间;slicing/quality 帧质量诊断
  • postprocess/pixelate 母版是像素画时吸附母版网格 + 锁母版色板,否则通用量化;postprocess/pack 脚线对齐 / 图集 / GIF;postprocess/rootmotion 逐帧时长
  • master_prep 按动作预处理母版

4. 对外契约与分流(windup_ai_engine ports / strategy / impl)

  • CharacterGeneratorPort.generate(card, action, master, progress) -> GeneratedAction,server 只 import ai_engine.ports,由 import-linter 强制
  • ROUTE_MATRIX 动作类型 → 生成路线
  • CharacterGenerator 选路线 → strategy.derive 出帧 → 脚线对齐 → 出参

边界(与作者对齐)

ai_engine 只产出帧 bytes + 逐帧时长,不碰存储 / 数据库 / 任务状态。母版由 server 从 Character.reference_image_url 取好以 bytes 传入;产出的帧由 server 上传对象存储、写 character_data。故本层无 ArtifactStorePort

生成路线矩阵(架构契约)

动作 路线 状态 依据
walk / run VIDEO_I2V 实测通 逐帧独立生成锁不住"哪条腿在前"→ 踢踏舞;视频天生连贯、腿自然交替
jump / attack VIDEO_I2V 实测通 同上;但属一次性动作,抽帧不闭环,走 pick_oneshot
idle VIDEO_I2V 实测通 见下方与 #53 的差异 ①
hit PER_FRAME 未实现,调用即抛错 离散姿势,单帧可编辑价值高、无连续步态

改此矩阵 = 改产线,需实测支撑。依据见 #35

#53 原设计的三处差异

# #53 原设计 现状 依据
idle → PROC_IDLE(¥0 程序化局部呼吸 Idle-B) idle → VIDEO_I2VPROC_IDLE 枚举与 ProcIdleStrategy 一并移除 程序化呼吸做不出可用效果,放弃,认这份 i2v 的钱
抠图 = rembg onnxruntime 直跑 u2netp rembg 依赖链在 3.12 无轮子;同模型同质量
视频 = kling-video-o1,v2-5-turbo / v2-1 已下架 v2-5-turbo 未下架 2026-07-27 复测收回该结论

关键缺陷修复

问题 影响 修复
未实现的路线返回 [b""] * n_frames 调用方拿到帧数对、时长对、无异常的 GeneratedAction——完全像一次成功的生成。server 会把 N 个 0 字节文件传上对象存储并写入 character_data,用户看到 N 张裂图,排查时不会想到是路线没实现 边界上抛错:strategy 调用即抛 NotImplementedError;装配表缺该路线时抛错并报出已装配了哪些;strategy 吐出空帧时抛 ValueError
抠图 provider 在 onnxruntime 缺失时静默降级为"取四角主色 chroma-key" 白底母版四角即白色,浅色角色(骨白 / 银甲)与背景撞色被抠穿;且静默——开发机上看着能跑、输出实际是坏的 改为抛 RuntimeError;绿幕抠图另立 provider,背景色显式给出,不猜
align_bottom_center 三条缩放分支只按高度定标 横向长条主体缩放后宽度超出画布,被 alpha_composite 以负 dest 静默丢像素,PIL 不报错。裁切悬崖 w/h ≈ 1.61;实测某四足角色母版 w/h=1.78 丢 27px(鼻尖 + 尾尖),w/h=2.0 只剩 79.9% 内容 加宽度兜底 fill_w=0.96。人形 w/h 0.3–1.1 时该约束恒不生效,产物逐像素不变
步态周期误检(三个坑,见下) 半周期闭环 = 末帧接回首帧时左右腿瞬间互换 见下
视频成品下载单次读取、无重试、不校验长度 该步发生在提交任务、轮询、等待全部成功之后,费用已产生、视频已生成,连接断一次整单作废。实测同一角色连续两单死在此处各烧一次费用 三次退避重试 + 长度校验(#129,已单独修复并合入 upstream 开发分支,本次随 PR 2 迁入)

周期检测:三个坑与实测

坑 1|平移偏置。 角色在画面里横向位移,d(p) 被"挪了多远"主导、随 p 单调上升,真周期的凹陷被抬平,argmin 滑到搜索窗边界交出假周期。
实测某走路视频(源 121 帧,搜索窗 p ∈ [16,72]):未消平移时曲线 40/56 段在上升,只剩 22 / 42 / 52 三个浅坑,argmin = 22;加 _deskew 消整体平移后,整条曲线只剩一个局部极小,正是真周期 56(凹陷深度 2.68)。

坑 2|搜索窗过窄。 上界 n//2 把真周期挡在窗外(某待机视频真周期 62 > pmax 60)。改为 total*0.6

坑 3|谐波。 平移偏置让 d(p) 偏爱短 lag,常选中真周期的约 1/2。改为在基周期整数倍里按归一化接缝(末→首帧差 ÷ 组内相邻帧差均值)复选,并优先取最小倍数——倍数越大 = 一个 loop 里塞进越多周期 = 每周期帧数越少 = 动作变糙。

无周期时不硬闭环。 凹陷深度 < 0.25 判"测不到可信周期",退化成全片均匀取。
实测某待机视频(源 31 帧,搜索窗 p ∈ [6,18]):未消平移时曲线单调,argmin 落在搜索窗下界 6,交出纯粹的边界假值;消平移后 p=11 虽是局部极小,但其值 14.010 夹在 14.013 与 14.023 之间,凹陷深度 0.006,被门槛挡掉。

四段真视频实测(n=16;接缝越接近 1 越闭合)

视频 旧 · 接缝 旧 · 逐像素重复帧 新 · 接缝 新 · 重复帧
走路 A 3.07 0 1.96 0
走路 B 1.29 0 0.87 0
待机 9.31 16 1.39 0
奔跑 1.77 0 0.81 0

待机一行:旧算法写出的 GIF 只有 6 帧——16 帧里 10 帧逐像素重复,被 PIL 自动去重。

消融。 改善全部来自 _deskew + 谐波复选。另行追加的「死帧避让 + 冻结裁剪」两条,两个样本无变化、两个变差(奔跑接缝 0.81 → 2.00),已回退quality.py 保留作诊断,不进选帧。

测试覆盖

文件 用例 覆盖
test_ai_engine_skeleton.py 9 路由矩阵契约、端到端串联、未实现路线抛错、空帧拒绝、已移除枚举
test_loop.py 4 周期检测与循环选帧
test_oneshot.py 5 一次性动作裁区间
test_pixelate.py 8 像素化、母版规格探测、色板锁定
test_matte_provider.py 2 抠图 provider
test_sufy_video_download.py 5 视频下载重试与长度校验

四条抛错相关的用例已拿修复前的旧实现对照,确认在修复前全部失败。


对抗式自检

以下四条是评审历史上会被追问的点。Q1–Q3 给出答案与其失效边界;Q4 是自检抓出的自相矛盾,已在提交前改掉。

Q1|ROUTE_MATRIX 固定在这一层对吗?为什么不让调用方传路线?

固定在 ai_engine.strategy。理由:路线选择依据是动作的物理性质(有无连续步态、是否一次性、是否需要单帧可编辑),不是调用方的业务决定。让 server 传等于把实测挣得的结论推给不掌握依据的一方。

但这个理由在渲染出帧路线进来后不成立——同一个 walk 既可走 i2v 也可走渲染,选择依据变成"该角色有没有 3D 模型",那是 server 才知道的事。

该边界已写进 strategy/base.py 的注释,以免后来者按错误前提扩展:接入第三条路线前须先定「路线选择由谁决定」,并可能要把矩阵改成「动作类型 → 可选路线集合」+ 一个选择器。

Q2|GeneratedAction{frames, durations, fps} 三个字段够吗?

对视频路线够。对渲染路线不够:出帧台在产出帧的同一时刻还能给出 root_motion / sample_times / fps_equiv / 源片段时长,这些只有渲染当时拿得到——帧出完后在图像空间只能反推位移,采样时刻与源片段时长反推不回来。

已在 #122 提出扩展,本 Issue 不改,避免同一契约两处并行修改。

Q3|为什么 ai_engine 不碰存储?边界依据是什么?

依据是"谁掌握租户与配额上下文"。上传对象存储要知道 bucket、路径规则、归属项目、配额;这些全在 server。ai_engine 若自持存储,等于把租户概念下沉到一个只应该做图像计算的层。

代价是帧 bytes 要在内存里过一次。当前 16 帧 512×512 RGBA ≈ 16MB,可接受;帧数或分辨率显著上升时需重新评估。

Q4|自相矛盾:删 PROC_IDLE 的理由是"不留没有实现的枚举值",却加了同样没有实现的 RENDER_3D

自检发现的矛盾,已在提交前改掉:RENDER_3D 已移除。

原本保留它的理由是「契约先留位,将来接入时 GenRoute 不必二次改形,避免一次跨模块的
破坏性变更」。该理由不成立:全仓 grep 确认 GenRoute 只在内存里当分流键,无持久化、
无序列化,枚举加成员是纯加法,不构成破坏性变更。理由塌掉后,留位就只剩「在代码里
表达意图」,而按团队规范意图应写进 Issue 不写进代码。

三渲二的契约需求由 #81 / #122 承载,随实现一起加枚举成员。

GenRoute 现在只有 VIDEO_I2V / PER_FRAME,并由 test_genroute_only_lists_implemented_routes
锁死——该用例同时管住两个方向:已证否的路线要删,未来路线不提前留位。


需要评审拍板(本 Issue 不改,列出待议)

以下四条是自检时发现的既有契约问题,修改会动 windup_common 的公共契约,故不在本批 PR 内处理。

它们有共同根因:契约按逐帧路线(route A)设计,而落地的是视频路线。n_framesposes 推导、
CharacterCard 零读取、loop / view / palette 零消费方,都是同一副旧设计骨架的残留。

D0|CharacterCard 六个字段在 ai_engine 里零读取

generate(card, action, master, progress)strategy.derive(card, ...) 都接收 card
但全仓 grep card\.ai_engine零命中——name / desc / palette / view /
master_ref / version 一个都没被读过。

原因是视频路线的角色身份由母版图像承载,不由提示词承载:_build_prompt 只取
action.facing,服装 / 鞋子走默认值,所以不同角色跑出来的提示词是同一份。

这不是缺陷,是路线特性。但它意味着:CharacterCard 目前是为逐帧路线设计、却只在视频
路线上被传递的占位参数
。逐帧路线要用 desc 组提示词,渲染路线要用 model_3d_ref#122),
两条都还没实现。

建议:明确它是「为未实现路线预留的入参」并在 ports docstring 里写清,或暂时从签名移除、
随逐帧路线一起加回。此处不单方面决定。

D1|ActionSpec.n_frameslen(poses) 推导,但视频路线不用 poses

@property
def n_frames(self) -> int:
    return len(self.poses)

视频路线取 n = action.n_frames or 8。调用方要出 16 帧,必须传一个 16 元素的 poses 列表,而列表内容在视频路线上完全不被使用。

建议:n_frames 改为显式字段,poses 仅逐帧路线使用;n_frames 缺省时再回退到 len(poses)

D2|枚举风格前后不一致

ActionType / GenRouteEnum;而 ActionSpec.loop / stylize / facingCharacterCard.view 是裸 str,合法值只写在注释里(none/linear/pingpongpixel/noneside/frontside/topdown/isometric)。

facing 尤其要紧:master_prep 里写死的契约是"提示词朝向必须与母版一致,给正面母版喂侧走词会让模型靠转身调和图文矛盾"。这条约束靠字符串传递,拼错不报错。

建议:四者都改枚举。

D3|CharacterCard.palettestr 但无格式约定

字段存在但没有消费方,也没写清是色板名、十六进制串还是存储引用。

建议:定格式或删除。


第二轮对抗自检:可生成性与质量信号的缺口

第一轮自检查的是契约形状,这一轮查的是输入不合作时会怎样。结论:本批实现对任意输入
都会走完全程并报成功,失败全部是静默的

以「一张人物在画板前作画的图,请求 walk」为例,逐段追踪:

行为 报错
generate() 入口 不校验母版能否解码、有无主体、有无腿、朝向是否与 facing 一致
prepare_master walk 不做处理,原图直传
_build_prompt 只传 action.facing,提示词是固定的侧走模板,与母版内容无关
i2v 图文矛盾(图在作画、词在走路)→ 模型靠转身 / 变形调和(master_prep 已记录该机制)
matte.cutout 显著性抠图,全仓无连通域筛选 → 画板被当主体一并保留
pick_cycle 测不到步态周期 → 凹陷深度 < 0.25 → 静默降级为全片均匀取
align_bottom_center 按整幅 alpha bbox 定标,画板每帧都在,中位数同样被撑大 → 角色系统性缩小
出参 16 帧、逐帧时长齐、无异常

调用方得到一个"看起来成功"的结果。这与本 Issue 已修的「未实现路线返回空帧」属同一类问题,
只是发生在产品层而非代码层。

E1|输入侧零校验

portsimpl 内所有 raise 都在输出侧(空帧、未实现路线)。master: bytes 不做
任何前置判定。

建议:加可生成性预检并允许拒绝——母版可解码 / 有主体 / 主体连通域数 / 走路类动作是否可见腿 /
朝向与 facing 是否一致。

E2|pick_cycle 四条返回路径,调用方无法区分

帧数不足           → 原样返回
测不到可信周期      → 均匀取,不闭环
候选为空           → 均匀取,不闭环
正常               → 单周期闭环

四条返回类型相同、无任何标记。上层无法判断这 16 帧是干净的步态循环还是降级产物。

E3|抠图无连通域筛选

OnnxU2NetMatteProvider.cutout 直接用显著性 mask 合成,抠出几个连通域就保留几个。
画板、道具、第二个角色都会被保留。

E4|对齐实现与产品规则不一致

产品规则要求「脚底线 + 躯干中心锚点对齐,忽略剑 / 围巾等延展物」。实现为:

canvas.alpha_composite(crop, (cell // 2 - w // 2, int(cell * foot_line) - h - lift))

水平方向取的是 bbox 中心而非躯干中心;延展物仅靠"各帧高度取中位数"抵抗单帧异常
(如某帧武器高举),对每帧都存在的延展物(画板 / 披风 / 坐骑)无效。

E5|出参无质量信号

GeneratedAction{frames, durations, fps} 无法表达"这次生成成色如何"。而所需指标本仓已具备
——归一化接缝、死帧数、周期可信度均可由 slicing/qualityslicing/loop 给出,只是不进出参。

建议:出参增加质量字段,让上层据此决定交付 / 重试 / 提示用户换母版。

E6|提示词硬编码,不构成一个可运营的能力

prompt/*.py 为写死模板;build_walk_prompt(garment, feet, facing) 三个参数中实际只传
facing,服装 / 鞋子取默认值,CharacterCard.desc 未进入提示词(即 D0)。因此不同角色
使用同一份提示词。当前无版本、无 A/B、无回滚,调整一次需发一次代码。

提示词是"如何让模型听话"的知识,会随模型版本漂移,且用户有自定义诉求(前端曾出现
ActionType.CUSTOM + action_desc 的形状)。建议单列为独立能力,本期先把注入口留出:
_build_prompt 目前写死 builder 表,应改为可注入。

测试覆盖的已知空白

本批 34 个用例每个都有断言,无空跑用例。但以下模块的公开函数零直接覆盖:

模块 未覆盖函数
slicing/quality frame_deltas dead_frame_mask active_span blur_ratio
postprocess/rootmotion extract_root_motion frame_durations
prompt/actions build_idle_prompt build_attack_prompt
master_prep add_headroom prepare_master

其中 frame_durations 参与每次出参构造、prepare_master 参与每次 jump 生成,属实际执行
路径上的未覆盖点。

以上 E1–E6 与测试空白均不在本批 PR 修改范围,建议单开 Issue「可生成性判定与质量门禁」
承载。当前契约是「给我母版,我给你帧」,缺的是「判断这个母版能否做这个动作」与「告知这次
成色如何」两层。


补充:视频接口面已变更(2026-08-07 实测)

本节推翻上文对 PR 2 的部分描述,与之冲突时以本节为准。

现状

SufyVideoProvider 建在过时的接口形状上:打 /v1/videos,首帧走 input_reference
base64 dataURI。而平台当前有 69 个 POST 视频端点,其中 22 个是图生视频,全部是
FAL 队列格式 /queue/...,全部要 image_url 公网 URL

发现经过:用一张全新角色母版跑 kling-v3-omni 端到端,费用已产生。任务 completed、
16 帧齐、逐帧时长齐,选帧指标反而是历史最好的一组(接缝 0.66、零重复帧、零死帧),
但画面里是一个与母版毫无关系的写实路人,且只有下半身——提示词里 "the legs clearly
visible" 被当成了取景指令。参考图从未被采纳。

kling-v3-omni 的图生视频真实地址是 /queue/fal-ai/kling-video/o3/{mode}/image-to-video
/v1/videos 上给它塞 input_referenceimage_list,四种字段形状全部试过,都不
可能成功——地址就是错的。

已按现行接口重写

新增 FalQueueVideoProvider 与显式的模型→端点映射表(10 个模型),保留
SufyVideoProvider 不动(/v1/videos 对 sora 系可能仍有效,无证据说它坏了)。

规范里挖出四处不能靠直觉推断的地方:

事实
轮询地址 不是提交地址加 /requests。六个 kling 模型共用 /queue/fal-ai/kling-video/requests/{id},模型段与 {mode} 全部消失;vidu 也丢掉 q3/pro
认证 FAL 端点是 Authorization: Key {key},不是 Bearer
base 路径 AI_BASE_URL 结尾是 /v1,而 /queue 是它的兄弟。不剥掉 /v1,每个调用都是 404
mode 枚举 各模型不同:v2.6 只有 pro,v3-turbo 是 standard/pro没有 std)。现默认 mode="std" 在两个模型上会 400,故构造时即拒绝

另:请求体没有 model 字段(模型即路径),duration 有三种写法("5" / "8s" / 5)。

契约冲突与解法

VideoProvider.i2v(first_frame: bytes, ...) 收 bytes,FAL 要公网 URL。

未改 Protocol。 新增 FirstFrameUploader port,作为 FalQueueVideoProvider必填
无默认构造参数
——构造不出一个没有首帧发布途径的 FAL provider。

不选另外三条的理由:

  • 改 Protocol 收 URL → 把"取公网 URL"推给所有调用方,包括需要 bytes 的 SufyVideoProvider
    且让对象存储成为整条管线的关切,与 ai_engine ports 的"不碰存储"边界矛盾
  • provider 自持上传 → 要在 framework/providers 里塞一个对象存储客户端,无凭证无法测试;
    windup_framework/storage/ 目前是空的,那条线该由存储的负责人来落
  • i2v 加可选 URL 参数 → 一个实现必需、另一个忽略的参数,污染共享契约

影响面:当前零调用点。 git grep .i2v( / VideoProvider 只命中 provider 自身与文档字符串。
新增义务只落在组装层:构造 FAL provider 时必须提供 uploader。

测试 37 个用例全 mock、零网络;变异测试 11/11 全部被捕获。

未验证

  • 从未真实调用过 FAL 端点。 认证方式、/v1 剥离、请求体形状、轮询、结果回退、下载,
    全部只对着 OpenAPI 规范与 mock 验证过。首次真跑会花钱,且可能暴露不一致
  • 三份规范(seedance-2.0 / vidu-q3-pro / kling-v3-turbo)称图字段接受 "URL 或 base64",
    另 19 份只写 URL。而实测四种 base64 形状均未被采纳。实现统一要求 http(s),对 22 个都安全
  • 哪个模型对我们的角色效果最好——未测。映射表是能力清单,不是推荐

补充:抠图(2026-08-07 实测)

两个问题

一、u2netp 对闭合区域天然失灵。 四足角色腿间的背景是一块被主体围住的空隙,显著性
模型把它当成主体内部,整块底色留在产物里;轮廓上还带一圈底色描边。

二、底色不可指定。 母版生成提示词写 SOLID MAGENTA #FF00FF,模型实际给 #DE297C
(偏离 140);改要求纯绿 #00FF00,给 #9ACC5D(偏离 190)。两次都不听。 因此
"规定一个底色然后按死值抠"这个思路不成立,只能采样实际底色。

修法必须窄

实测一个铁锈橙毛 (222,130,70) 的角色配玫红底 (222,41,124):两者红通道完全相同
欧氏距离仅 104。先后试过两版宽阈值 chroma,都把橙毛判成半透明并去"反解",越解越坏
(先成橄榄绿、再成亮绿)。

最终解法:u2netp 定主体,只杀 d < 38近似精确等于底色的像素。橙毛 d≈117
完全不受影响,闭合空隙 d≈0 干净移除。三个角色底色残留
2.54% / 0.44% / 1.25% → 0.17% / 0.21% / 0.26%,主体内部逐像素未动。

与"按颜色抠是死路"那条规则的边界:那条说的是拿颜色当主体判据(白底浅色角色会被
抠穿)。这里主体判据仍是 u2netp,颜色只用来做减法,绝不新增主体像素;底色不够均匀
时(四角标准差 > 8)直接跳过。

顺带去掉 onnxruntime 缺失时"猜四角主色"的静默兜底,改为抛错。

方法论教训

修这个问题的过程中造了三个残留指标,全部漏判了橙毛被改坏——因为橙毛的 alpha 是
0.62,不在"内部像素 α>250"的统计范围里。每次都是先看画面才发现指标在骗人。涉及画面
的改动,指标只能用来复核,不能用来验收。


补充:新增验证(2026-08-07)

Holdout(8 段从未参与调参的真视频)pick_oneshot 首次在真视频上验证——它在
jump/attack 主路径上,此前只有合成数据的单元测试。4 段攻击视频无重复帧,选中段的运动
强度是全片平均的 1.7–2.3 倍。pick_cycle 4 段中 3 段干净,唯一告警是一段近乎静止的早期
废片,算法正确地判"无周期"并退化成不硬闭环。

主路径补测frame_durations(每次出参构造都走)与 prepare_master(每次 jump/attack
生成都走)此前零直接覆盖,补 21 个用例,变异测试 5/5 全部被捕获。

代码去重_gray() + _SMALL=48slicing/loop.pyslicing/quality.py 各有一份
完整拷贝;_png()/_img()strategy/concrete.pyimpl/character_generator.py 各一份。
两组都收成唯一定义。这类分叉不会报错,只在数据或画面上体现。

画布裁切修复的真实样本:新生成的四足角色主体 w/h = 1.92,超过 1.61 裁切悬崖。修复前
鼻尖与尾尖被切平、主体贴死画布左右边缘;修复后完整落在画布内。两个人形角色(w/h 0.48 /
0.70)修复前后逐像素相同,确认该约束不误伤既有资产。


关联

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

P1优先级 P1(次级)enhancementNew feature or request

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions