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,124 @@
|
||||
"""一次性迁移:把 `wallpapers/<游戏>/<壁纸>/` 从「按文件类型分组」改成「一具骨架一组」。
|
||||
|
||||
旧:
|
||||
effects/<名>.json ← 骨架数据
|
||||
images/<名>.atlas images/<名>*.webp ← atlas 与页(扁平)
|
||||
images/<名>/<名>.atlas + <名>.png ← 另一档用的「一骨架一目录」
|
||||
images/scene/<图> ← 场景图
|
||||
images/ava.jpg ← 背景图
|
||||
新:
|
||||
spines/<名>/<名>.json | <名>.atlas | <页…>
|
||||
scene/<图> ← 场景图与背景图
|
||||
audios/… ← 不动
|
||||
|
||||
规则(页的判定用 **atlas 自己声明的页名**,与运行时同一份真相,不做名字启发式):
|
||||
1. 每个 `<atlas>` 的 stem 建 `spines/<stem>/`,把该 atlas 与它声明的页搬进去;
|
||||
2. `effects/*.json` → `spines/<stem>/<stem>.json`;
|
||||
3. `images/` 里剩下的文件(含 `images/scene/*`)→ `scene/`;
|
||||
4. 按实际搬动结果重写 `preset.template.json` 里所有 `./…` 路径。
|
||||
|
||||
用法:
|
||||
python .scratch/migrate-walls.py # dry-run,只打印
|
||||
python .scratch/migrate-walls.py --apply # 真搬 + 改预设
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
WALLS = ROOT / "wallpapers"
|
||||
APPLY = "--apply" in sys.argv
|
||||
|
||||
PAGE_EXT = re.compile(r"\.(png|webp|jpg|jpeg)$", re.IGNORECASE)
|
||||
|
||||
|
||||
def atlas_pages(text: str) -> list[str]:
|
||||
"""取 atlas 声明的页名(非属性行、以图片扩展名结尾的行)。"""
|
||||
pages = []
|
||||
for raw in text.splitlines():
|
||||
line = raw.strip()
|
||||
if line and ":" not in line and PAGE_EXT.search(line):
|
||||
pages.append(line)
|
||||
return pages
|
||||
|
||||
|
||||
def migrate(wall: Path) -> dict[str, str]:
|
||||
"""搬一档壁纸,返回 {旧相对路径: 新相对路径}(相对壁纸目录,不带 ./)。"""
|
||||
moves: dict[str, str] = {}
|
||||
|
||||
# 1) atlas 及其声明的页
|
||||
atlases = list((wall / "images").rglob("*.atlas")) + list((wall / "spines").rglob("*.atlas"))
|
||||
for atlas in atlases:
|
||||
stem = atlas.stem
|
||||
dest_dir = wall / "spines" / stem
|
||||
pages = atlas_pages(atlas.read_text(encoding="utf-8"))
|
||||
moves[str(atlas.relative_to(wall))] = f"spines/{stem}/{atlas.name}"
|
||||
for page in pages:
|
||||
src = atlas.parent / page
|
||||
if src.exists():
|
||||
moves[str(src.relative_to(wall))] = f"spines/{stem}/{page}"
|
||||
|
||||
# 2) 骨架 json
|
||||
for js in (wall / "effects").glob("*.json") if (wall / "effects").is_dir() else []:
|
||||
stem = js.stem
|
||||
moves[str(js.relative_to(wall))] = f"spines/{stem}/{js.name}"
|
||||
|
||||
# 3) images/ 下剩下的(含 images/scene/*)→ scene/
|
||||
if (wall / "images").is_dir():
|
||||
for path in sorted((wall / "images").rglob("*")):
|
||||
if path.is_dir():
|
||||
continue
|
||||
rel = str(path.relative_to(wall))
|
||||
if rel in moves:
|
||||
continue
|
||||
moves[rel] = f"scene/{path.name}"
|
||||
|
||||
# 4) 重写预设(按映射精确替换 ./ 路径)
|
||||
preset_path = wall / "preset.template.json"
|
||||
preset = json.loads(preset_path.read_text(encoding="utf-8"))
|
||||
replaced: list[str] = []
|
||||
|
||||
def rewrite(value: object) -> object:
|
||||
if isinstance(value, str) and value.startswith("./"):
|
||||
old = value[2:]
|
||||
new = moves.get(old)
|
||||
if new:
|
||||
replaced.append(f"{value} → ./{new}")
|
||||
return f"./{new}"
|
||||
if isinstance(value, dict):
|
||||
return {k: rewrite(v) for k, v in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [rewrite(v) for v in value]
|
||||
return value
|
||||
|
||||
new_preset = rewrite(preset)
|
||||
|
||||
print(f"\n== {wall.relative_to(ROOT)}")
|
||||
for old, new in sorted(moves.items()):
|
||||
print(f" {old:52} → {new}")
|
||||
for line in replaced:
|
||||
print(f" 预设: {line}")
|
||||
|
||||
if APPLY:
|
||||
for old, new in moves.items():
|
||||
src, dst = wall / old, wall / new
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(str(src), str(dst))
|
||||
# 清掉空目录
|
||||
for empty in ["effects", "images"]:
|
||||
d = wall / empty
|
||||
if d.is_dir():
|
||||
shutil.rmtree(d, ignore_errors=True)
|
||||
preset_path.write_text(json.dumps(new_preset, ensure_ascii=False, indent="\t") + "\n", encoding="utf-8")
|
||||
return moves
|
||||
|
||||
|
||||
total = 0
|
||||
for wall in sorted(p for p in WALLS.rglob("*") if (p / "preset.template.json").is_file()):
|
||||
total += len(migrate(wall))
|
||||
print(f"\n{'已搬动' if APPLY else '将搬动'} {total} 个文件;{'(dry-run,加 --apply 才真搬)' if not APPLY else ''}")
|
||||
Reference in new issue
Block a user