zijunchiang
|
1acc75f00e
|
test(release): 逐文件核对「发布包 == 打标签的树」(T-WEB-018)
「发布包里的东西必须等于打标签的那棵树」此前靠人抽查几个文件(README、contracts/VERSION…),
必然会漏。上一轮实测就暴露了两个认知错误:Web 的发布包其实**包含** web-server/test/**
(33 个文件,我此前以为不含),以及包内另有 177 个**构建产物**(admin/、web-server/dist/)
必须与工作树构建比 —— 只比源码文件是不够的。
新增 web-server/scripts/check-tracked-release.py(纯标准库,支持 .tar.gz / .zip):
对包内**每一个**文件按路径决定比较基准 —— 构建产物(admin/、web-server/dist/)比工作树
构建结果,其余文件比 `git show <tag>:<路径>`;任何内容不一致 / 包内多余(既不在 git 也不是
声明的构建产物)/ 构建产物在工作树缺失都会列出并以非 0 退出。`npm run release:check` 已注册。
实测(**已发布**资产重新下载后跑):
包内 356 个文件:源码/文档 179(与 tag v0.7.5 比)+ 构建产物 177(与工作树构建比)
✓ 逐文件一致:0 处不一致 / 0 处多余 / 0 处缺失
反向验证:篡改 README.md 一个字节 + 塞入 web-server/EXTRA.txt →
✗ 内容不一致 1 处(README.md)、✗ 包内多余 1 处(EXTRA.txt),退出码 1。
顺带修掉脚本自身一个路径映射缺陷:BUILD_OUTPUTS 的键若是不带 / 的**精确文件名**
(Windows/Android 的单文件产物),原实现会把剩余路径拼成 ".../OpenDance.exe/"
导致「工作树缺失」误报;现在区分「目录前缀」与「精确文件名」两种键。
注:accept-release.sh 的接入只在下一个版本的产物里生效(本仓 accept-release.sh 本身
也在发布包内,v0.7.5 已发布);对已发布的 v0.7.5 包仍可用 `npm run release:check -- <包> --tag v0.7.5`
逐文件核对(已跑通)。同时已把同一脚本复制到 opendance-windows / opendance-android 并接入
两端的 accept-release.sh。
|
2026-09-18 03:10:05 +08:00 |
|
zijunchiang
|
5a78749e49
|
test(codec): 钉住关键点帧的跨实现语义 + 提供一致性向量生成器(T-WIN-021)
`web-server/src/preprocess/keypoint-codec.ts` 是契约 websocket-keypoints §2 的
**参考实现**(其 encodeBinaryFrame 注释即「Android 端参考实现」,
decodeBinaryFrame 注释即「Windows 端参考实现(T-WIN-004)」)。Android 已用
「金标准字节向量」与它对齐,而 **Windows 的 GDScript 解码器此前只跟自己比过**
(自己编码、自己解码)—— 那条边由本仓暴露出的向量来补。
新增:
* scripts/gen-frame-vectors.mts(+ npm run vectors:frame):用参考实现生成 12 个
用例的字节与**参考解码结果**,供 Windows 仓的 accept-frame-vectors.sh 锁定比对。
向量里同时给出 inputPoints(原始 float64 输入)、f32InputPoints / f32Hex
(收敛到 float32 的**同输入**基准)与 points(参考解码结果),各自注明用途。
* test/keypoint-frame-codec.spec.ts(7 项):把参考实现对其他两端的承诺显式钉住 ——
i16 最小值边界(-32768/32767 必须是 0x8000,不能被夹成 -32767)、
**取整规则**(JS Math.round 半数朝 +∞:-2.5 → -2,与 Android 的 Math.round 一致)、
置信度阈值 128/255 与两端夹紧、u32 frame_id(0xFFFFFFFF / 0x80000000)、
flags 只取低 8 位、非法帧(长度 / magic / 版本)拒绝、20 组随机输入的往返量化误差。
实测:npm run verify 全绿 —— 契约 6 项 / 35 路由 / 版本一致 / 26 文件 302 项测试
(原 295)/ build / 产物级实体注册核对 / 启动冒烟。
|
2026-09-17 05:51:26 +08:00 |
|
zijunchiang
|
06b476c4e2
|
test(accept): 产物级端到端验收自动化 + 实体注册核对落到 dist(v0.7.4 实测全通过)
背景:`npm run verify` 跑的是源码与刚构建的 dist,而交付给部署方的是
`dist-release/*.tar.gz`;两者之间隔着「解包 → npm install --omit=dev → 迁移 →
起进程」四步。本项目三次生产事故(v0.7.0/v0.7.1 起不来、v0.7.3 场景上传 400、
v0.7.4 `No metadata for "Scene"`)全都只在产物真正跑起来时才暴露。
一、`scripts/check-bootstrap.mjs` 新增「产物级实体注册核对」
直接读包内 `dist/entities/`:逐个 import 每个 *.entity.js(@Entity() 装饰器
在模块加载时写 metadata),要求
* 每个实体文件里被 @Entity() 装饰的类都在 ENTITIES 里;
* ENTITIES 里不允许有未装饰的成员;不允许重复;
* Scene 必须注册(点名)。
为什么必须单独有这一条:漏注册实体**不影响启动**(TypeORM 只按 ENTITIES 注册,
DI 图照样解析、/health 正常、migrationsPending 0),只在第一次查该实体时抛
`No metadata`,导致 /api/scenes、/api/catalog/export、/api/catalog/version、
/api/reviews?status=pending 全部 500 —— 客户端一个目录都拉不到。
原来的启动冒烟与 entities-registry.spec.ts(查 src/)都抓不到它。
反向验证(在真实 v0.7.4 发布包里复现生产缺陷):把包内
dist/entities/index.js 的 ENTITIES 去掉 Scene 后,核对立即失败并给出
「scene.entity.js 里的 Scene 没有注册进 ENTITIES」与「ENTITIES 里没有 Scene」;
还原后通过。
二、`scripts/accept-release.sh`(新增):一个 tar.gz 的产物级端到端验收
解包 → 装生产依赖 → 跑迁移 → 起服务 → 校验 /health 关键字段
(版本 / 数据库 / migrationsPending / scenes.available)→ 产物级实体注册核对
(用仓库当前的 checker 覆盖包内旧版本,验收标准以最新为准)→ 跑发布包自带的
test/smoke.mjs → 停服务。只需要一个现成的 MySQL/MariaDB,不装数据库
(与项目「不部署 MySQL」的边界一致)。
实测(opendance-web-v0.7.4.tar.gz + 真实 MySQL 9.7.2,全新库从零迁移):
✓ /health version=0.7.4 与 package.json 一致
✓ 数据库已连接(opendance_accept,pending=0)
✓ scenes.available=true(生产 v0.7.3 是 false + No metadata)
✓ 产物级实体注册核对通过:6 个实体文件全部注册进 ENTITIES(含 Scene)
== 结果:80 通过 / 0 失败 ==
冒烟 [7] 段即 ④ 验收:POST /api/scenes 201、一律 pending、GLB 直传不重编码、
glbUrl 生成、重力/风/摩擦/坡度/出生点/相机模式按配置原样入库、越界夹紧与非法
枚举回退、审核通过后 catalog/export 的 scenes[] 带完整物理参数与 GLB/缩略图字段、
目录指纹纳入场景变更、详情、状态过滤、DELETE 204。
`npm run verify` 全绿:契约 6 项 / 35 路由 / 版本一致 / 295 项测试 / build /
产物级注册核对 / 启动冒烟。
|
2026-09-17 04:44:30 +08:00 |
|
zijunchiang
|
f3b97fec53
|
fix(scenes): 场景表不可用不再拖垮整站(生产事故修复)+ 诊断脚本
现场(真实部署 v0.7.2):数据库缺 scenes 表,于是
/api/scenes、/api/scenes/:id、/api/catalog/export、/api/catalog/version、
/api/reviews?status=pending 全部 500,而 /api/songs、/api/motions、/api/models
正常。后果不是"场景不可用",而是客户端拉不到任何目录(Windows/Android 全废)、
管理端待审列表打不开。`/health` 却显示 database.connected=true、
migrationsPending=0 —— 因为 DataSource.showMigrations() 只比较**代码里加载到的**
迁移;部署的 dist 若缺 AddScenes.js,该迁移不在比较集合里,于是"没有待执行迁移"
但表不存在(最隐蔽的一种不一致)。
修复思路:契约与文档都写明「场景是可选资源」(没有可用场景时客户端回退内置空
场景),所以场景表的问题不允许影响与场景无关的功能 —— 改为降级:
- ScenesService 新增 queryScenes() 包装:数据库层面失败时返回空结果,**只记一次
日志**(避免刷屏),原因写入 diagnostics();下一次查询成功即自动复位;
- ReviewsService.listByStatus() 对场景查询单独 catch,待审列表照常返回其它资源;
- /health 新增 scenes: { available, error } —— 降级但绝不静默;
- HealthModule 增加 imports: [ScenesModule](HealthController 注入了 ScenesService,
Nest 的 DI 按模块解析,漏了会直接 UnknownDependenciesException;
scripts/check-bootstrap.mjs 已能拦住这一类)。
新增 web-server/scripts/diagnose-scenes.mjs(默认只读):打印运行期真实连的库、
表清单、typeorm_migrations 记录、**代码实际加载的迁移**、scenes 实际列 vs 实体
期望列的逐列 diff;`--fix` 仅在缺表时用迁移里同一份 DDL 幂等补表。
顺带修正 models.controller.ts 的误导性注释:模型上传**不接受** isPrimary/status
(契约:上传一律 pending,默认模型由审核通过时带 setPrimary=true 或
POST /api/models/:id/primary 设定)。test/smoke.mjs 此前正是按错误预期断言
"isPrimary 已置位",已改为:上传断言 pending + isPrimary=false → 审核通过时带
setPrimary=true → 再断言 isPrimary=true;同时给目录导出/指纹等检查补上失败细节
(HTTP 状态 + 响应体),并把待审列表查询的 pageSize 放大到 200(避免库里待审项多
时新条目被挤出第一页)。
文档:README.md/.txt 增加 v0.7.3 修订说明;tasks/015-scene-management.md 增
「缺陷修复记录二」;开发文档 §4.12 补充硬性要求(场景不可用必须降级 + /health 暴露
+ showMigrations 的陷阱说明)。
测试:新增 test/scenes-degradation.spec.ts(6 项);npm run verify 通过
(契约六项一致、35 条路由、21 文件 / 276 项测试、nest build、启动冒烟);
发布产物解压后 npm ci --omit=dev + node dist/main.js 实测打印「应用版本 v0.7.3」。
注意:Gitea 附件上传通道仍故障(1 KB 也瞬时 500),因此本版**尚未上传 Release**,
产物先在本地交付(sha256 见 SHA256SUMS)。
|
2026-09-17 03:24:56 +08:00 |
|
zijunchiang
|
2433cd22a5
|
fix(boot): CatalogModule 漏 import ScenesModule 导致产物起不来 + /health 版本号错
两个都是**只有真正启动服务才会暴露**的缺陷,v0.7.0 / v0.7.1 的产物受影响。
1. 【致命】CatalogModule 漏 import ScenesModule
CatalogService 因为 catalog/export 要下发 scenes[] 而注入 ScenesService,但
CatalogModule 的 imports 只列了 SongsModule / MotionsModule / ModelsModule。
Nest 的 DI 按模块解析,于是 node dist/main.js 在 bootstrap 阶段直接抛
UnknownDependenciesException: Nest can't resolve dependencies of the
CatalogService (... ? ...) —— 服务完全起不来(装得上、起不来)。
为什么三层检查全绿:nest build/tsc 看不出运行时 DI;258 项单元测试直接 new
服务或测纯函数,从不编译 AppModule;打包脚本只校验文件清单与压缩包完整性。
修复:CatalogModule.imports 补上 ScenesModule。
2. /health 的 version 在原生部署下永远是 0.1.0
原实现 process.env.npm_package_version ?? '0.1.0',而该变量只在 npm 脚本下
存在;正式部署是原生服务(ExecStart=node dist/main.js),npm 不参与 ->
恒为 undefined -> 回退到硬编码 0.1.0,与管理端左下角展示的 vX.Y.Z 冲突,
违反文档 11.2 第 4 条「可核对:Web 端至少与 GET /health 的 version 一致」。
修复:新增 src/config/app-version.ts 作为运行时唯一解析点
(APP_VERSION -> 运行入口旁/工作目录的 package.json(带包名校验)
-> npm_package_version -> 显式 0.0.0),/health 改用 resolveAppVersion(),
main.ts 启动首行也打印版本号。新增 12 项测试,其中一项显式模拟无 npm
环境变量的原生部署。
防线(都挂进了 verify 与打包脚本)
- scripts/check-bootstrap.mjs:以正式部署方式(node dist/main.js、无 npm 环境
变量、不需要数据库)拉起真实产物,断言①无依赖解析类错误②启动日志的应用版本
等于 package.json ③已推进到数据库连接阶段。已做反向验证:删掉 ScenesModule
后该检查立刻失败。
- scripts/check-version.mjs(并入 contracts:check):校验
web-server/package.json = admin/package.json = project.yaml,正则禁止
health.controller.ts 再出现硬编码版本回退,管理端已构建时断言产物 JS 含 vX.Y.Z。
- npm run verify 现在包含 bootstrap:check;package-release.sh 在构建后、打包前
各跑一次(构建管理端后再跑 version:check,因为此时才需要校验产物内版本)。
文档:README.md / README.txt 增加 v0.7.2 修订说明;
tasks/011-version-display.md 与 tasks/015-scene-management.md 补「缺陷修复记录」
(含为什么当时没被发现的三层检查对照表)。
验证:npm run verify 通过(契约六项一致、35 条路由、258 项测试、nest build、
启动冒烟);产物 opendance-web-v0.7.2.tar.gz
sha256 f04ecc85d17517ec00754238616eb1237cdb2fe17440af6dce731827c0922b26。
|
2026-09-17 01:49:52 +08:00 |
|
zijunchiang
|
c342a804a1
|
feat(models): 新增历史模型回填(reanalyze)并修复存储去重的悬挂文件问题
背景:升级到带 models.stats 的版本后,库里已有模型没有分析与缩略图,
审核界面会全部显示"未分析、无预览图"。E2E 验证时还发现一个真问题:
sha256 去重命中旧资产但文件不存在(换过 STORAGE_DIR、或只恢复数据库)时,
分析会静默产出"全 0 结果"。
- POST /api/models/:id/reanalyze:重新分析(面数/骨骼树/humanoid/可驱动性)
并重建缩略图,只改 stats 与 thumbnailAssetId;文件在存储中不存在时返回 409,
避免用空结果覆盖已有分析数据
- scripts/reanalyze-models.mjs:批量回填 CLI(--dry-run / --model-id / --force)
- StorageService.persistBuffer:去重命中但文件不存在时按原路径**自愈补写**
- 契约:rest-api 新增 /api/models/{id}/reanalyze(路由 29 → 30 条)
- 文档:README / tasks/005 / 发布说明补回填章节与升级步骤
实测:真实 21MB VRM(290 骨骼)回填出面数 53552 / 顶点 44662 / humanoid 7/22,
并重建 512×512 缩略图;文件缺失场景返回 409 且不覆盖既有数据。
测试 168 项通过;契约与 30 条路由一致。
|
2026-09-15 21:18:15 +08:00 |
|
zijunchiang
|
53bf182476
|
feat(models): 发布 v0.2.0 —— GLB/VRM 主力格式、自动缩略图与分析、模型审核与骨骼预览
需求 1:系统确定以 GLB / VRM 为模型主力格式
- 服务端新增支持库:
- model-inspector.ts:解析 GLB/VRM 的面数、顶点、材质、贴图、形变目标、
**骨骼树(名称/层级/是否蒙皮/humanoid 映射/子节点数)**、humanoid 覆盖率与可驱动性
- model-thumbnail.ts:**纯 TS 软件光栅化**(z-buffer + Lambert + 完整蒙皮矩阵)
生成 512×512 PNG 缩略图,零原生依赖、不装 headless-gl
- vrm-writer.ts:GLB → VRM 1.0(写入 VRMC_vrm 扩展与 humanoid 映射)
- gltf-packer.ts:glTF(.gltf + .bin + 贴图)→ 单文件 GLB
- humanoid 候选表与 Godot 客户端 humanoid_map.gd **逐字对齐**,并有单测断言两边一致
(服务端判定的"可驱动" = 客户端实际能否驱动)
需求 2:模型管理
- .glb / .vrm / .gltf **直接导入**(不重编码,GLB 含 VRM 扩展会自动识别为 VRM)
- .pmx / .fbx 服务端转换:assimp → .glb →(失败)→ .gltf 后自行打包成 GLB
→(MODEL_CONVERT_FORMAT=vrm 时)补 VRMC_vrm 产出 VRM 1.0;每步 assimp info 回读校验
- .zip **自动搜索包内可用模型**:.vrm → .glb → .gltf(直接导入)优先,
其次 .pmx → .fbx(转换);同优先级内按"指名字 → 目录最浅 → 文件最大"
- 入库后自动分析(models.stats)与生成缩略图(models.thumbnail_asset_id),
失败均不阻断入库;管理端列表显示小缩略图、预览放开到 vrm/glb/gltf
需求 3:审核功能
- 上传的模型状态**一律 pending**(不再自动发布、不再自动设默认)
- 审核界面新增模型面板:体积、面数、顶点、材质、贴图、骨骼、形变目标等指标,
humanoid 覆盖进度、可驱动性结论(缺哪些必需骨骼)、warnings,以及
**骨骼详细预览**(层级缩进、蒙皮/Humanoid 徽章、子节点数、搜索保留祖先链、
仅看蒙皮、限高滚动 + 只渲染前 400 行)
- 审核通过可带 setPrimary=true 一并设为默认模型(此时才写源码种子);
POST /api/models/:id/primary 仅允许 published(否则 409)
- scripts/import-default-model.mjs 改为"上传 → 审核通过 + 设默认"流程
其它:
- models 新增 stats 列(迁移 AddModelStats1789460000000,已在真实库执行)
- 契约升至 rest-api/data-models/model-formats 0.4.0、database 0.3.0(另两库已同步)
- 版本号 0.1.0 → 0.2.0(package.json ×2 + lockfile + project.yaml)
- 文档:README.txt / DSH.md / project.yaml / tasks 005·007·010 全面更新;
顶层开发书更新为 v1.4
验证:168 项单测通过(10 文件);契约 6 项与 29 条路由一致;typecheck/build 通过;
真实端到端:MMD 压缩包 → GLB + 分析(面数 9071/骨骼 238/humanoid 0-22) + 缩略图(15KB PNG);
GLB 直接导入 sha256 与源文件一致;审核通过后 defaultModelId 由 VRM 变为该模型
|
2026-09-15 20:37:53 +08:00 |
|
zijunchiang
|
ccced34dd6
|
fix(models): 默认模型改用「压缩包 sha256」匹配(转换产物非字节级确定)
实测发现:同一份 zip 连续转换三次得到三个不同的产物 sha256(大小都是 1,152,464
字节,差异集中在 BIN chunk 的十几字节),说明 Assimp 的 GLB 导出不是字节级确定的。
因此原先把「转换产物 sha256」当作默认模型匹配键的设计是错的——换一次转换就失效。
- default-model.ts / default-model-source.ts:种子拆成 sourceSha256(上传压缩包的
sha256,稳定,主匹配键)与 fileSha256(产物 sha256,仅作记录)
- models.service.ts:resolveDefaultModelId 依次按 压缩包 sha256 → 产物 sha256 →
名称 匹配,都落空才回落到 VRM 优先规则并告警
- ModelDto 增加 sourceSha256(非压缩包上传为 null)
- scripts/import-default-model.mjs:写种子时用 sourceSha256;顺带修掉位置参数解析
(文件当第一个参数时识别不出,会打印用法)
- 契约与文档同步:data-models §4.1、model-formats §1.1 记录该确定性结论;
README / DSH / tasks/005 的「按产物 sha256 匹配」表述已更正
另附:把模型格式能力的实测矩阵写进 Release 说明(原生只支持 glTF 系;
PMX→FBX 失败;VRM→GLB 丢 VRM 扩展;humanoid 骨骼命名覆盖率)。
验证:125 项单测通过;清理掉两个"文件只在本机临时目录"的模型(避免 sha256 去重
导致后续上传不落盘);种子按压缩包 sha256 命中后 defaultModelId 正确解析。
|
2026-09-15 19:36:37 +08:00 |
|
zijunchiang
|
e4351c5062
|
feat(models): 模型压缩包自动转换 + 默认模型写进源码;首次启动 .env 引导;HOST 默认 0.0.0.0
需求 1:模型上传支持压缩包(MMD 模型包)
- common/zip-reader.ts:纯 TS 极简 ZIP 读取器(EOCD/中央目录、STORE+DEFLATE、
ZIP64 大小字段、Shift-JIS/GBK 文件名回退、拒绝 zip-slip 与加密条目)
- models/model-convert.ts:解压 → 选主模型(指名字/最浅/最大)→ Assimp 导出 FBX
→ **assimp info 回读校验**(Assimp 对带蒙皮大骨架模型会写出损坏文件:
实测 714 骨骼 PMX 导出退出码 11、产物回读报 FBX-DOM no Objects dictionary found)
→ 失败自动回退 GLB;两种都失败则 422
- models/glb-textures.ts:把 Assimp 写成外部 URI 的贴图内嵌进 GLB 的 BIN chunk,
保证模型库里单文件即可加载(否则是白模)
- 溯源:压缩包原件存为 model-archive 资产;models 新增 source_asset_id /
conversion_note(迁移 1789450000000-AddModelConversionFields,已在真实库上
验证 up/down 可逆)
- 默认模型:转换成功且 autoDefault 不为 false → is_primary=1 + published,
并把身份(名称/格式/产物 sha256)写进源码 src/config/default-model.ts;
resolveDefaultModelId 增加 DEFAULT_MODEL_ID 与源码种子两步(按 sha256 匹配,
换库仍有效),实测库中有 2 个 VRM 时默认模型仍解析为该 GLB
- scripts/import-default-model.mjs:运维 CLI(上传 zip 并写源码 / --write-source-only)
- 修复 multipart 中文文件名乱码(upload.util.decodeUploadName)
- DTO/契约新增 conversionNote、fileSha256;管理端 accept 增加 .zip 并展示转换来源
需求 2:首次启动 .env 引导
- deploy/init-env.sh:交互式逐项填写 8 个键(回车保留默认、口令不回显不加引号、
端口校验、mysql2 连通性自检且口令只经环境变量传递、只改这 8 个键并保留模板注释、
chmod 600、结尾打码摘要);--force/--check/-y/-i/--skip-db-check 等开关;
非交互(-y、OPENDANCE_NONINTERACTIVE、stdin 关闭)绝不提问、读取带超时
- install.sh 缺 .env 时改为调用引导,失败才 die
需求 3:默认监听 0.0.0.0
- 代码默认值、两份 .env 模板、install.sh、init-env.sh 五处一致,并新增测试守护
(未设置 HOST 时为 0.0.0.0;两份模板都写着 HOST=0.0.0.0)
契约:rest-api / data-models / model-formats → 0.3.0,database → 0.2.0(另两库已同步)
验证:125 项单测通过(7 文件);契约 6 项与 29 条路由一致;typecheck/build 通过;
真实包端到端 HTTP 201 / 0.89s / format=glb / isPrimary=true / published;
GLB 1,152,464 字节、1 mesh / 240 nodes / 1 skin(238 joints) / 2 张内嵌贴图;
迁移 revert→run 实测可逆且数据无损恢复
|
2026-09-15 13:55:54 +08:00 |
|
zijunchiang
|
bba5bcf59e
|
feat(web): 端口分离 + VMD 动作支持;契约升至 0.2.0
需求 1:管理端端口与软件 API 端口分离
- 软件 API 端口 PORT(默认 3000)只提供 /api、/health、/static、/contracts,
不提供管理页面(未命中路径 404);管理端端口 ADMIN_PORT(默认 3001)
提供管理页面与全部接口;ADMIN_HOST 可单独限制管理端来源
- main.ts 同进程监听两个端口,用 req.socket.localPort 区分端口行为;
ADMIN_PORT=PORT 为单端口兼容模式(只创建一个 server,无 EADDRINUSE)
- 端口非法在生产环境中止启动;ADMIN_PORT=PORT 只 warn
- 启动日志分别打印两个端口的访问地址
需求 2:动作数据支持 VMD(MikuMikuDance)
- vmd-parser.ts:二进制解析(30 字节头 + Shift-JIS 模型名 + 111 字节/条骨骼
关键帧 + morph 段)、MMD 贝塞尔插值、GBK 模型名回退、固定 30fps
- mmd-skeleton.ts:标准 MMD 骨架;静止比例从参考 PMX 骨骼段实测得到
(足首 1.43 / 膝 6.57 / 髋 11.50 / 首 16.21 / 頭 17.16,腿长 10.09)
- mmd-pose.ts:FK + 两骨骼解析腿部 IK(膝盖朝正面,不可达收敛为伸直)
+ 左手系→右手系 Z 镜像(由真实数据对照实验确定:94.1% vs 5.9%)
- retarget.ts:抽出 worldFramesToKeypointFrames(),BVH 与 VMD 共用重定向
- MotionFormat 增 vmd、扩展名映射增 .vmd;管理端上传支持 .vmd
- scripts/inspect-vmd.mjs:上传前自查工具
契约:rest-api / data-models / model-formats → 0.2.0(另两库已同步逐字节一致)
验证:85 项单测通过(VMD 31 项);契约与 29 条路由一致;typecheck/build 通过;
真实样本经 HTTP 端到端:霜雪千年 2901 帧(IK 腿)、恋爱循环 4604 帧(FK 腿)
均 preprocessState=done、0 NaN、0 越界(测试数据已从数据库清理)。
|
2026-09-15 08:47:37 +08:00 |
|
zijunchiang
|
5a8b77b0a4
|
feat(web): T-WEB-010 部署脚本(Docker Compose,不含 MySQL)
|
2026-09-14 19:00:55 +08:00 |
|