feat: TS 构建管线、Spine 抓取器与 wallpapers/ 唯一真相来源
把项目从「手写 dist/」改成「wallpapers/ 是唯一真相来源,dist/ 由 pnpm build 生成」,
并补上配套的类型、门禁与抓取器。一次提交落地整条管线,因为拆开会留下不能构建的中间态。
- src/:运行时与模拟器源码(TS,strict),编译到 build/ 再拷进各分发
- tools/:build / dev / check-{syntax,paths,dist},以及抓取器与回归门禁 tools/checks/
(.scratch/ 下那批一次性脚本移入 tools/checks/ 并入库为长期门禁)
- wallpapers/:七档壁纸的源数据 + README.md(id/音频/预设的完整规范)
- docs/adr/0005-0008:构建管线与分发拓扑、模拟器契约、自包含 sim、每骨架资源布局
- .gitignore:排除 .scratch/ 的参考资料副本(上游 spine 整仓克隆 ~1.2 GB、
抓取侦查数据 ~680 MB)与调试转储;这些是本地调查材料,补偿会让仓库无法克隆
- 归一化 .gitignore/CONTEXT.md 行尾(工作区 CRLF、索引 LF 造成的整文件假 diff)
同时修掉三档卡住构建的未完工壁纸:
- kv45 的 meta.json 里 id 还是抓取期场景名 scene_main,经 downloader promote 正名为 kv45
- shajin / zhigengniao_juheye 的 meta.json 误用了骨架描述文件(name/spine/animations/pages)、
且都缺 preset.template.json;现按规范重建:骨架沉到 spines/<名>/(spine-ts 按 atlas 所在
目录解析贴图页)、补上元数据与单骨架预设,并清掉 zhigengniao 骨架里指向作者机的绝对路径
- 顺带 promote 已在 sources.yml 里的 kv46(月升之前,与兽共舞)
pnpm check 五道门全绿:10 个分发 / 7 档壁纸 / 141 处引用自包含。
This commit is contained in:
1 parent
b8eee05d78
commit
3f11426964
297 files changed
+216627
-1926
No files matched your search
@@ -0,0 +1,309 @@
|
||||
// 构建期共享类型:schema 本身即文档。
|
||||
//
|
||||
// 三层元数据 + 两层运行时配置的关系:
|
||||
//
|
||||
// wallpapers/meta.json ← 「所有壁纸合集」这一档的发布文案
|
||||
// wallpapers/<gameId>/meta.json ← 游戏显示名 + 该游戏的共享音频
|
||||
// wallpapers/<gameId>/<wallpaperId>/
|
||||
// meta.json ← 壁纸的发布文案与音频清单(运行时用不到的)
|
||||
// preset.template.json ← 壁纸的运行时配置(播放器真正要读的)
|
||||
//
|
||||
// build 把后两者合成两份产物:preset.js(运行时)与 project.json(发布)。
|
||||
|
||||
import type { ViewportSpec } from "../../src/globals.d.ts";
|
||||
|
||||
/** WE 属性的合法类型。build 只生成 combo,其余类型由模板手写。 */
|
||||
export type PropertyType =
|
||||
| "bool"
|
||||
| "combo"
|
||||
| "slider"
|
||||
| "textinput"
|
||||
| "color"
|
||||
| "file"
|
||||
| "directory"
|
||||
| "texture"
|
||||
| "group"
|
||||
| "usershortcut"
|
||||
| "text";
|
||||
|
||||
export interface PropertyOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface PropertyDef {
|
||||
type: PropertyType;
|
||||
text?: string;
|
||||
value?: unknown;
|
||||
order?: number;
|
||||
index?: number;
|
||||
options?: PropertyOption[];
|
||||
condition?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
precision?: number;
|
||||
fraction?: boolean;
|
||||
}
|
||||
|
||||
/** project.json 的骨架(src/project.template.json)。 */
|
||||
export interface ProjectTemplate {
|
||||
contentrating: string;
|
||||
file: string;
|
||||
ratingsex: string;
|
||||
ratingviolence: string;
|
||||
tags: string[];
|
||||
type: string;
|
||||
visibility: string;
|
||||
general: { properties: Record<string, PropertyDef> };
|
||||
}
|
||||
|
||||
/**
|
||||
* meta.json 里**手写**的一条音源声明:只有显示名与文件路径。
|
||||
*
|
||||
* id 不在这里写——它由 build 自增分配(见 AudioChoice)。以前是手写字符串
|
||||
* (zaiduheni / xilian / pv37),加一条就得现编一个不重名的 id。
|
||||
*/
|
||||
export interface AudioChoiceSpec {
|
||||
/** 显示名(中文原样保留,是产品的一部分)。 */
|
||||
name: string;
|
||||
/** 相对**本壁纸目录**或**合集根**的文件路径(不含 ./ 前缀也可)。 */
|
||||
file: string;
|
||||
}
|
||||
|
||||
/** meta.json 里的一份音频声明。 */
|
||||
export interface AudioDecl {
|
||||
/** 默认音源的位置,**1 起**;省略 = 第一个。 */
|
||||
default?: number;
|
||||
choices: AudioChoiceSpec[];
|
||||
}
|
||||
|
||||
/** build 解析后的音源:id 已经分配好,是 WE combo 的 value。 */
|
||||
export interface AudioChoice {
|
||||
/**
|
||||
* 自增 id,**全项目唯一**("1"、"2"、"3"…)。
|
||||
*
|
||||
* 为什么必须全局唯一而不是每档壁纸各从 1 开始:`bgm` 下拉会把一个分发里**所有**壁纸的
|
||||
* 音源平铺进同一个 combo,两档壁纸都叫 "1" 的话,选中的到底是哪一个就无从分辨了。
|
||||
* 分配顺序固定为 游戏 → (该游戏的共享音频 → 各壁纸的音源),所以同一个音源在任何分发里
|
||||
* 拿到的 id 都一样。
|
||||
*/
|
||||
id: string;
|
||||
name: string;
|
||||
file: string;
|
||||
}
|
||||
|
||||
/** 壁纸级元数据(wallpapers/<gameId>/<wallpaperId>/meta.json)。 */
|
||||
export interface WallpaperMeta {
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
/** 预览图,相对本目录。缺省时 build 省略 project.json 的 preview 并 warning。 */
|
||||
preview?: string;
|
||||
/** 该档壁纸可选音源(本壁纸目录内的目录)。 */
|
||||
audio: AudioDecl;
|
||||
/** 上传 Workshop 后由 WE 生成;此处缺省即不写字段。 */
|
||||
workshopid?: string;
|
||||
workshopurl?: string;
|
||||
}
|
||||
|
||||
/** 游戏级元数据(wallpapers/<gameId>/meta.json)。 */
|
||||
export interface GameMeta {
|
||||
id: string;
|
||||
name: string;
|
||||
/**
|
||||
* 游戏合集的创意工坊标题。**必填**:这一层是「游戏合集」文案的唯一来源,
|
||||
* 不许回落全局 meta——那层是给「全部合集」写的(回落过,见 .scratch/build-pipeline/issues/20)。
|
||||
*/
|
||||
title: string;
|
||||
/** 游戏合集的文案;缺省时 build 用「共 N 档」的模板自动生成。 */
|
||||
description?: string;
|
||||
/** 该游戏的共享音频;只在「合集」类分发里被使用。 */
|
||||
audios: AudioChoiceSpec[];
|
||||
}
|
||||
|
||||
/** 全局元数据(wallpapers/meta.json)= 「所有壁纸合集」这一档。 */
|
||||
export interface GlobalMeta {
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
/** 默认预设 id:决定 preset 下拉的第一项与 project.json 的 preset.value。 */
|
||||
defaultPresetId?: string;
|
||||
/** 分发级预览图(相对 wallpapers/ 的文件名)。缺省时 project.json 省略 preview。 */
|
||||
preview?: string;
|
||||
workshopid?: string;
|
||||
workshopurl?: string;
|
||||
}
|
||||
|
||||
/** 壁纸级运行时配置(preset.template.json)。只放播放器真正要读的东西。 */
|
||||
export interface PresetTemplate {
|
||||
/** 背景图,相对本目录。 */
|
||||
backgroundImage: string;
|
||||
/**
|
||||
* 单骨架路径的配置。与 `sceneConfig` **二选一**:
|
||||
* 两个都写或都不写,构建期直接报错——不留静默优先级,歧义会烂在产物里。
|
||||
*/
|
||||
spineConfig?: {
|
||||
jsonUrl: string;
|
||||
atlasUrl: string;
|
||||
animation?: string;
|
||||
viewport?: ViewportSpec;
|
||||
/** "author" = 保留作者原始取景,不跟随背景缩放。 */
|
||||
framing?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/**
|
||||
* 场景路径:N 具骨架 + M 块贴图平面,按抓取期 `scene.json` 的世界变换与绘制层级合成。
|
||||
* 这些字段由抓取器的 `scene.json` 抄来(构建期烘焙),运行时不再读侧车。
|
||||
*/
|
||||
sceneConfig?: {
|
||||
/** 页面场景的 UI 尺寸,仅作退化取景的兜底。 */
|
||||
ui?: number[];
|
||||
/**
|
||||
* 页面相机(抓取期从场景树抄来)。
|
||||
* `type: 1` = 透视:按 fov 与相机 z 做 billboard 投影(z 是真实景深,不能忽略);
|
||||
* `type: 2` = 正交:各 part 直接按原坐标画(z 全为 0 的那些页面)。
|
||||
*/
|
||||
camera?: { type?: number; fov?: number; position?: number[] };
|
||||
/** 页面是 y 向上、spine 渲染是 y 向下:默认把 y 取反;实测颠倒时设 false。 */
|
||||
flipY?: boolean;
|
||||
/** 时间线的整场位移(页面单位),从真页面量出来;见 .scratch/scene-player/perspective.md。 */
|
||||
timelineOffset?: number[];
|
||||
parts: {
|
||||
kind: "spine" | "image" | "solid";
|
||||
id: string;
|
||||
order: number;
|
||||
renderOrder?: number;
|
||||
position: number[];
|
||||
scale: number[];
|
||||
image?: string;
|
||||
jsonUrl?: string;
|
||||
atlasUrl?: string;
|
||||
animation?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
/** 网格中心相对节点原点的偏移(页面坐标系);四边形不以原点为中心时用它。 */
|
||||
center?: number[];
|
||||
/** 节点旋转(弧度)。非零 = 被倾斜的 3D 面片,运行时当前**不画**它。 */
|
||||
rotation?: number[];
|
||||
/** 页面指定的皮肤(`spine.skin`);`animation` 字段本来就在上面。 */
|
||||
skin?: string;
|
||||
timeScale?: number;
|
||||
color?: number;
|
||||
}[];
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/** 内存里的一份壁纸(已读盘、已校验)。 */
|
||||
export interface Wallpaper {
|
||||
id: string;
|
||||
gameId: string;
|
||||
/** 相对仓库根的源目录,如 wallpapers/hsr/xilian。 */
|
||||
srcRel: string;
|
||||
meta: WallpaperMeta;
|
||||
preset: PresetTemplate;
|
||||
game: GameMeta;
|
||||
/**
|
||||
* meta.json 里的原始音频声明(id 还没分配)。
|
||||
* `defaultIndex` 已经是 **0 起**的数组下标,把"1 起"的换算收在 vault 里一处。
|
||||
*/
|
||||
audioDecl: { defaultIndex: number; choices: AudioChoiceSpec[] };
|
||||
/** 该档壁纸的音频清单:id 已由 build 分配,文件路径已解析成相对**分发内壁纸目录**的形式。 */
|
||||
audioChoices: AudioChoice[];
|
||||
}
|
||||
|
||||
/** 内存里的一份游戏。 */
|
||||
export interface Game {
|
||||
id: string;
|
||||
meta: GameMeta;
|
||||
/** 相对仓库根的源目录,如 wallpapers/hsr。 */
|
||||
srcRel: string;
|
||||
wallpapers: Wallpaper[];
|
||||
/** meta.audios 分配 id 之后的结果(读盘时还没有 id,见 readVault)。 */
|
||||
sharedAudio: AudioChoice[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 分发类型:**单档**与**合集**两种并列。
|
||||
*
|
||||
* 「游戏合集」与「全部合集」都是合集,区别只在 `CollectionScope`——它们不是第三种类型。
|
||||
* (术语见 CONTEXT.md:单档 / 合集。)
|
||||
*/
|
||||
export type ReleaseType = "single" | "collection";
|
||||
|
||||
/** 合集的收档范围:一个游戏,还是所有游戏。只有 `type === "collection"` 时有意义。 */
|
||||
export type CollectionScope = "game" | "all";
|
||||
|
||||
/** 一次构建里的一个分发目标。 */
|
||||
export interface Release {
|
||||
type: ReleaseType;
|
||||
/** 合集的收档范围(单档分发没有它)。 */
|
||||
scope?: CollectionScope;
|
||||
/** 稳定的分发 id:单档用壁纸 id,游戏合集用游戏 id,全部合集用 "all"。 */
|
||||
id: string;
|
||||
/** 目录名(ASCII,带类型前缀,避免"合集"与"单档"混淆)。 */
|
||||
dir: string;
|
||||
/** 该分发包含的壁纸(single 恰含一个)。 */
|
||||
wallpapers: Wallpaper[];
|
||||
/** 展示名,写进 dist-map.json 供 dev 索引页使用。 */
|
||||
displayName: string;
|
||||
/** 该分发的发布文案(单档分发直接取壁纸自己的 meta)。 */
|
||||
meta: { title: string; description: string; preview?: string; workshopid?: string; workshopurl?: string };
|
||||
/** 上面那份 meta 所在的目录(相对仓库根):preview 指向的文件从它旁边取。 */
|
||||
metaRel: string;
|
||||
/**
|
||||
* 该分发"合集根共享音频"清单(相对合集根的文件名)。
|
||||
* 单档分发恒为空——非合集分发不出现共享音频。
|
||||
*/
|
||||
sharedAudio: AudioChoice[];
|
||||
}
|
||||
|
||||
/** dist-map.json:分发清单,也是 dev 服与校验脚本的唯一索引。 */
|
||||
export interface DistMap {
|
||||
version: string;
|
||||
builtAt: string;
|
||||
releases: {
|
||||
dir: string;
|
||||
type: ReleaseType;
|
||||
/** 合集的收档范围(单档分发省略)。 */
|
||||
scope?: CollectionScope;
|
||||
id: string;
|
||||
displayName: string;
|
||||
/** 该分发里每档壁纸的 id 与其在目录内的相对路径。 */
|
||||
wallpapers: { id: string; gameId: string; dir: string; name: string }[];
|
||||
/** 该分发的默认预设 id(写进 scripts/presets.js)。 */
|
||||
defaultPresetId: string;
|
||||
/** 发布产物字节数(**不含** sim/)。 */
|
||||
bytes: number;
|
||||
/** 自包含包 sim/ 的字节数;没跑 --sim 时不存在。 */
|
||||
simBytes?: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface BuildOptions {
|
||||
singles: string[];
|
||||
collect: "all" | string[] | null;
|
||||
/**
|
||||
* `pnpm build single`(不给 id)= 只编全部单档,不编合集。
|
||||
*
|
||||
* 与"什么都不选"(= 全编)必须区分开:单档子命令的语义是"范围=单档",
|
||||
* 而不是"没指定所以全都要"。用一个显式标记而不是靠 singles 为空来判断。
|
||||
*/
|
||||
onlySingles?: boolean;
|
||||
/**
|
||||
* `pnpm build collect`(不给参数)= 全部合集:每个游戏的合集 + 「全部壁纸合集」。
|
||||
*
|
||||
* 与 `collect: "all"` 区分开:后者是 `--collect all`,只编「全部壁纸合集」一项。
|
||||
* 这两个语义确实容易混,但都是既有行为,改动其中一个会悄悄改变发布范围。
|
||||
*/
|
||||
allCollections?: boolean;
|
||||
strict: boolean;
|
||||
clean: boolean;
|
||||
dryRun: boolean;
|
||||
/** 额外产出双击可看的自包含包(<分发根>/sim/index.html)。 */
|
||||
standalone?: boolean;
|
||||
/** 自包含包里是否内联音频。 */
|
||||
embedAudio?: boolean;
|
||||
}
|
||||
Reference in new issue
Block a user