feat(scene): 场景选择与物理落地 + 整包发布(T-WIN-012/013/014)

T-WIN-013:API 升级与场景选择
- 契约快照同步到 rest-api 0.5.0 / data-models 0.7.0 / database 0.4.0
- catalog_types.gd 新增 SceneConfig(物理/环境/出生点/相机)与 Scene;
  CatalogBundle 增加 scenes[];from_dict 按契约 7.1 再收敛一次
  (缺项默认、越界夹紧、非法枚举回退),与服务端 normalizeSceneConfig 同规则
- 新增 src/scene/scene_runtime.gd:GLB 载入 -> 碰撞体(mesh 三角网 /
  auto 命名约定 *_col、*_colonly / none 不兜底)-> 场景级重力(Area3D
  SPACE_OVERRIDE_REPLACE,不改全局 ProjectSettings)-> 风(每物理帧
  apply_central_force,阵风正弦叠加,支持 local 坐标系)-> 环境光与雾
  -> 出生点与相机(follow/fixed/orbit);无碰撞体时兜底地面
- CatalogStore 增加 scenes/default_scene/find_scene/ensure_scene_cached/
  fetch_thumbnail(场景缓存带 sha256 指纹,避免换包后仍用旧 GLB)
- 主界面改为 2x2 四个选择器(曲目/动作/模型/场景)+ 场景参数面板
  (缩略图 + 重力/摩擦/弹性/碰撞体/风摘要)
- 游戏界面载入场景并落地物理,角色落在出生点;场景缺失/下载失败/
  解析失败一律回退内置空场景并提示,不阻塞开始跟练

T-WIN-012:「关于」清单展示当前版本号
- 版本唯一来源为 project.godot 的 application/config/version(v0.2.0)
- about_manifest() 输出版本/引擎/契约/协议/地址/隐私清单;
  无头测试无 autoload,读取 AppConfig 走 get_node_or_null 容错
- Credits 的单个 CONTRACT_VERSION 常量改为 CONTRACT_VERSIONS 字典
  (六项契约各自独立递增),并双向核对 contracts/VERSION

T-WIN-014:发布必须是单个完整包
- build/package-release.sh 重写:只产出单个 zip + SHA256SUMS,
  清理历史 .partN,超限时告警而非回退分卷;新增源产物与解压后完整性
  自检(PE 头 MZ / pck 魔数 GDPC / exe+pck 同目录同名)与体积-上限对比
- deploy/proxy 相关的 Gitea IIS 256MB 上限说明见 opendance-web

测试:tests/test_scene_config.gd(12 例)+ tests/test_about.gd(4 例);
无头测试 687 通过 / 0 失败(10 个套件),verify.sh 全部通过
This commit is contained in:
2026-09-17 00:45:23 +08:00
parent 47e11af7d0
commit 5581cbe05d
27 changed files with 2031 additions and 85 deletions
+92
View File
@@ -940,4 +940,96 @@ components:
songs: { type: array, items: { $ref: '#/components/schemas/Song' } }
motions: { type: array, items: { $ref: '#/components/schemas/Motion' } }
models: { type: array, items: { $ref: '#/components/schemas/Model' } }
scenes:
type: array
description: 已发布场景(rest-api 0.5.0 起随目录下发;结构见 data-models.md 7)
items: { $ref: '#/components/schemas/Scene' }
defaultModelId: { type: integer, format: int64, nullable: true }
Scene:
allOf:
- $ref: '#/components/schemas/SourceFields'
- type: object
properties:
id: { type: integer, format: int64 }
name: { type: string }
glbUrl: { type: string, nullable: true, description: 场景 GLB 下载地址 }
thumbnailUrl: { type: string, nullable: true }
fileSize: { type: integer, nullable: true }
fileSha256: { type: string, nullable: true }
conversionNote:
type: string
nullable: true
description: 由 .zip/.pmx/.fbx 转换而来时的说明;直接上传 GLB 为 null
config:
allOf: [{ $ref: '#/components/schemas/SceneConfig' }]
nullable: true
description: 物理 / 环境 / 出生点 / 相机;服务端只存参数,仿真在客户端
stats:
type: object
nullable: true
description: 场景 GLB 分析(面数 / 网格 / 材质 / warnings)
status: { $ref: '#/components/schemas/PublishStatus' }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
SceneConfig:
type: object
description: >-
data-models.md 7.1 冻结结构。服务端入库时对每项做边界收敛
(越界夹紧、非法枚举回退默认值),客户端拿到的参数一定可解释。
properties:
physics:
type: object
properties:
gravity:
type: object
description: 米/秒^2,各分量 ±200
properties:
x: { type: number }
y: { type: number }
z: { type: number }
wind:
type: object
properties:
base: { $ref: '#/components/schemas/Vec3' }
gustAmplitude: { type: number, description: 0~100 }
gustFrequency: { type: number, description: Hz,0.01~5 }
gustDirection: { $ref: '#/components/schemas/Vec3' }
space: { type: string, enum: [world, local] }
ground:
type: object
properties:
friction: { type: number, description: 0~1 }
restitution: { type: number, description: 0~1 }
slopeLimit: { type: number, description: 0~89 度 }
colliders: { type: string, enum: [auto, mesh, none] }
environment:
type: object
properties:
ambientColor: { type: string, description: '#RRGGBB' }
ambientIntensity: { type: number, description: 0~4 }
fogDensity: { type: number, description: 0~1,0=关闭 }
spawnPoints:
type: array
maxItems: 16
items:
type: object
properties:
x: { type: number }
y: { type: number }
z: { type: number }
yaw: { type: number, description: 朝向(度,绕 Y 轴) }
camera:
type: object
properties:
mode: { type: string, enum: [follow, fixed, orbit] }
distance: { type: number, description: 0.5~50 }
height: { type: number, description: 0~20 }
Vec3:
type: object
properties:
x: { type: number }
y: { type: number }
z: { type: number }