图库改走独立仓库 + 单提交历史(每次重烘 orphan + force push)
背景:Gitea 的 Release 附件在本实例不可用(1KB..59.5MB 全部 HTTP 500),所以图集包改为 发布到独立仓库 troytt/diablo2-web-assets,并刻意只保留一个提交——每次重烘在临时目录 git init 后提交当前包并 force push,仓库体积恒定在「一份快照」~66 MB;代码仓库 clone 保持 2.7 MB。 新增 scripts/publish-packs.ts(npm run publish:packs): - 统计块数/文件数/体积与 index.json sha256,生成图库仓库里的 README(用法、许可、重建步骤) - 写 .gitattributes(*.png binary -diff) - 在临时目录 init -b main → 拷贝 samples/d2-packs → 提交 → force push - 不落任何凭据到磁盘;remote 可用参数或 D2_ASSETS_REMOTE 覆盖 README「资源包从哪来」改为以图库仓库为主、归档镜像为辅;HANDOVER 记录两种发布方式、 服务端 gc 的注意事项(Gitea 的 cron.git_gc_repos 默认关闭,force push 后的旧对象需 gc 才回收)。
This commit is contained in:
parent
a1af1d7304
commit
cd81a7e686
10
HANDOVER.md
10
HANDOVER.md
|
|
@ -42,9 +42,13 @@ npm run net-server # 等价于 node scripts/net-server.ts 8787
|
|||
|
||||
```bash
|
||||
npm run pack:data # scripts/pack-act-assets.ts → samples/d2-packs(62 个地图块 / 35 关,约 67 MB)
|
||||
# 图集包不在 Git 里(每次重烘会让仓库再长 ~60 MB,PNG 无法 delta 压缩)。发布归档:
|
||||
# https://www.laiseek.xyz/diablo2/assets/d2-packs-<日期>.tar.xz (nginx → /var/www/d2assets/)
|
||||
# Gitea 的 Release 附件在本实例(124.221.104.39)是坏的:1KB..59.5MB 全部 HTTP 500
|
||||
# 图集包不在代码仓库里(每次重烘会让仓库再长 ~60 MB,PNG 无法 delta 压缩)。发布方式:
|
||||
# 1) 图库仓库 troytt/diablo2-web-assets:npm run publish:packs
|
||||
# → 每次 orphan 提交 + force push,仓库恒定"一份快照"(~66 MB),代码仓库 clone 仅 ~2.7 MB
|
||||
# 2) 归档镜像(可选):https://www.laiseek.xyz/diablo2/assets/d2-packs-<日期>.tar.xz
|
||||
# (nginx location /diablo2/assets/ → /var/www/d2assets/)
|
||||
# 注意:force push 后旧提交对象要服务端 git gc 才回收;Gitea 的 [cron.git_gc_repos] 默认关闭,
|
||||
# 而 Gitea 的 Release 附件在本实例(124.221.104.39)是坏的:1KB..59.5MB 全部 HTTP 500。
|
||||
npm run verify:packs # 与现读现解逐项比对:绘制序/坐标、碰撞栅格逐字节、每帧像素 FNV 哈希(992/992)
|
||||
npm run verify:listfile # 用社区 1.13c listfile 逐归档判定"这个成员到底存不存在"
|
||||
npm run verify:deploy # 线上 /diablo2/ 页面 + 资源包 + Range 206 + 旧入口 410(17/17)
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -46,7 +46,16 @@ scripts/ 资源包烘焙、格式/数据/部署校验、无头浏览器检
|
|||
|
||||
### 2. 资源包从哪来(不在 Git 里)
|
||||
|
||||
图集包(62 个地图块 / 35 关,索引色 PNG + `scene.json`)以归档形式发布,**不入 Git**:
|
||||
图集包(62 个地图块 / 35 关,索引色 PNG + `scene.json`)**不在本仓库里**——它是 ~66 MB 的索引色
|
||||
PNG,每次重烘几乎全部文件都变,而 PNG 无法被 git 的 delta 压缩,随仓库分发会让每烘焙一次仓库再长
|
||||
~60 MB。它放在**图库仓库**,那里刻意**只保留一个提交**(每次重烘 orphan 提交 + force push),
|
||||
体积恒定在「一份快照」:
|
||||
|
||||
```bash
|
||||
git clone https://git.projectdiablo2.cn/troytt/diablo2-web-assets.git samples/d2-packs
|
||||
```
|
||||
|
||||
只想要一份压缩包(不需要 git):
|
||||
|
||||
```bash
|
||||
curl -O https://www.laiseek.xyz/diablo2/assets/d2-packs-2026-09-14.tar.xz
|
||||
|
|
@ -54,8 +63,8 @@ sha256sum d2-packs-2026-09-14.tar.xz # 应为 9cdfd0c19ac68de842c6725070b65d05
|
|||
tar xJf d2-packs-2026-09-14.tar.xz # 解出 samples/d2-packs/
|
||||
```
|
||||
|
||||
同目录还有 `d2-packs-2026-09-14.sha256`。仓库里只保留 `.gitignore` 规则与本文档;
|
||||
要自己生成包就照第 3 步 `npm run pack:data` 从原版 MPQ 烘焙。
|
||||
仓库里只保留 `.gitignore` 规则与本文档;要自己生成包就照第 3 步 `npm run pack:data` 从原版 MPQ
|
||||
烘焙,再用 `npm run publish:packs` 发布到图库仓库(脚本会重建单提交历史)。
|
||||
|
||||
### 3. 本地跑起来
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
"verify:object-lookup": "node scripts/verify-object-lookup.ts",
|
||||
"port:object-lookup": "node scripts/port-object-lookup.ts",
|
||||
"verify:alignment": "node scripts/verify-tile-alignment.ts",
|
||||
"verify:tiles": "node scripts/verify-tiles.ts"
|
||||
"verify:tiles": "node scripts/verify-tiles.ts",
|
||||
"publish:packs": "node scripts/publish-packs.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.20.2",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* Publish `samples/d2-packs` to the asset repository.
|
||||
*
|
||||
* The baked atlas packs are ~66 MB of indexed PNG. PNG is already deflate
|
||||
* compressed, so git's delta compression buys almost nothing: every re-bake changes
|
||||
* nearly every file, and a normal commit would grow the repository by another ~60 MB
|
||||
* each time (measured: the code repository went 63 MB → 117 MB over two bakes).
|
||||
*
|
||||
* So the asset repository deliberately keeps **exactly one commit**: every publish
|
||||
* creates an orphan history (a brand new repository in a temp directory), commits the
|
||||
* current packs, and force-pushes it over the remote. The repository therefore stays
|
||||
* at "one snapshot" (~66 MB) no matter how often the packs are re-baked, and the code
|
||||
* repository — which no longer contains the packs — clones in a couple of megabytes.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* node scripts/publish-packs.ts # default remote
|
||||
* node scripts/publish-packs.ts <git-remote-url> # explicit remote
|
||||
* ```
|
||||
*
|
||||
* The remote needs credentials; either pass a URL with them, or run
|
||||
* `git config --global credential.helper store` once so `git push` does not prompt.
|
||||
* Nothing here writes a password to disk.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import {
|
||||
cpSync, existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync,
|
||||
} from 'node:fs'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
/** Where the packs live inside the code repository. */
|
||||
const PACKS_DIR = resolve(import.meta.dirname, '..', 'samples', 'd2-packs')
|
||||
|
||||
/** The asset repository, without credentials. */
|
||||
const DEFAULT_REMOTE = 'https://git.projectdiablo2.cn/troytt/diablo2-web-assets.git'
|
||||
|
||||
/** The code repository, linked from the generated README. */
|
||||
const CODE_REPO = 'https://git.projectdiablo2.cn/troytt/diablo2-web'
|
||||
|
||||
/** The archive mirror served next to the deployment. */
|
||||
const ARCHIVE_BASE = 'https://www.laiseek.xyz/diablo2/assets'
|
||||
|
||||
/** Raised when the packs are missing or git fails. */
|
||||
class PublishError extends Error {}
|
||||
|
||||
/**
|
||||
* Run one git command in `cwd`, inheriting stdio so progress is visible.
|
||||
*
|
||||
* @param args - git arguments.
|
||||
* @param cwd - directory to run in.
|
||||
*/
|
||||
function git(args: readonly string[], cwd: string): void {
|
||||
try {
|
||||
execFileSync('git', args, { cwd, stdio: ['ignore', 'inherit', 'inherit'] })
|
||||
} catch {
|
||||
throw new PublishError(`git ${args.join(' ')} failed`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the statistics that go into the generated README.
|
||||
*
|
||||
* @returns file count, total bytes, map-block count and the `index.json` hash.
|
||||
*/
|
||||
function measure(): { files: number; bytes: number; maps: number; indexSha: string } {
|
||||
const indexPath = join(PACKS_DIR, 'index.json')
|
||||
if (!existsSync(indexPath)) {
|
||||
throw new PublishError(`no packs at ${PACKS_DIR} — run: npm run pack:data`)
|
||||
}
|
||||
const index = JSON.parse(readFileSync(indexPath, 'utf8')) as { entries?: unknown[] }
|
||||
const indexSha = createHash('sha256').update(readFileSync(indexPath)).digest('hex')
|
||||
let files = 0
|
||||
let bytes = 0
|
||||
const walk = (dir: string): void => {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const path = join(dir, entry.name)
|
||||
if (entry.isDirectory()) walk(path)
|
||||
else {
|
||||
files += 1
|
||||
bytes += statSync(path).size
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(PACKS_DIR)
|
||||
return { files, bytes, maps: index.entries?.length ?? 0, indexSha }
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the README that ships inside the asset repository.
|
||||
*
|
||||
* @param stats - output of {@link measure}.
|
||||
* @param stamp - `YYYY-MM-DD` bake date.
|
||||
* @returns the markdown text.
|
||||
*/
|
||||
function readme(stats: ReturnType<typeof measure>, stamp: string): string {
|
||||
const mb = (stats.bytes / 1048576).toFixed(1)
|
||||
return `# d2web 烘焙图集包(${stamp})
|
||||
|
||||
这是 [\`troytt/diablo2-web\`](${CODE_REPO}) 的**烘焙产物**:把原版暗黑破坏神 II(1.13c)的地图
|
||||
解包成 web 原生格式——索引色 PNG 图集页 + \`scene.json\`(画家序绘制列表、碰撞 RLE、出生点、
|
||||
对象元数据)+ \`index.json\`(含各 act 调色板)。
|
||||
|
||||
- 地图块 **${stats.maps} 个**(35 个预置关卡,act1–act5),文件 **${stats.files} 个**,合计 **${mb} MB**
|
||||
- \`index.json\` sha256:\`${stats.indexSha}\`
|
||||
- 生成时间:${stamp}
|
||||
|
||||
## 本仓库为什么只有一个提交
|
||||
|
||||
图集页是索引色 PNG(deflate 已压缩),git 的 delta 压缩对它几乎无收益:每次重烘几乎全部文件
|
||||
都会变。若按普通方式提交,每烘焙一次仓库就再长 ~60 MB(实测代码仓库因此从 63 MB 涨到 117 MB)。
|
||||
所以这里刻意**只保留一个提交**:每次重烘用 orphan 提交 + force push 覆盖,仓库体积恒定在
|
||||
「一份快照」≈${Math.round(stats.bytes / 1048576)} MB。历史没有价值——需要旧版请从代码仓库
|
||||
对应提交重新烘焙。
|
||||
|
||||
## 怎么用
|
||||
|
||||
\`\`\`bash
|
||||
# 放进代码仓库的 samples/d2-packs/,页面默认就吃这个目录
|
||||
git clone ${DEFAULT_REMOTE} samples/d2-packs
|
||||
\`\`\`
|
||||
|
||||
只要一份压缩包(不需要 git):
|
||||
|
||||
\`\`\`bash
|
||||
curl -O ${ARCHIVE_BASE}/d2-packs-${stamp}.tar.xz
|
||||
tar xJf d2-packs-${stamp}.tar.xz
|
||||
\`\`\`
|
||||
|
||||
## 怎么重新生成并发布
|
||||
|
||||
在代码仓库里(需要自备原版 MPQ,放在 \`samples/d2/\`):
|
||||
|
||||
\`\`\`bash
|
||||
npm run pack:data # scripts/pack-act-assets.ts → samples/d2-packs(约 2 分钟)
|
||||
npm run verify:packs # 与"现读现解"逐项比对:绘制序、碰撞栅格逐字节、每帧像素哈希
|
||||
npm run publish:packs # orphan 提交 + force push 覆盖本仓库
|
||||
\`\`\`
|
||||
|
||||
## 许可
|
||||
|
||||
内容为原版游戏美术的解码产物,仅供**持有正版游戏数据**的人在本项目内使用;请勿再分发。
|
||||
原版 MPQ 与第三方参考源码都不在本仓库内。
|
||||
`
|
||||
}
|
||||
|
||||
const remote = process.argv[2] ?? process.env['D2_ASSETS_REMOTE'] ?? DEFAULT_REMOTE
|
||||
const stats = measure()
|
||||
const stamp = new Date().toISOString().slice(0, 10)
|
||||
const work = mkdtempSync(join(tmpdir(), 'd2-assets-'))
|
||||
|
||||
try {
|
||||
console.log(`图集包:${String(stats.maps)} 块地图 / ${String(stats.files)} 个文件 / ${(stats.bytes / 1048576).toFixed(1)} MB`)
|
||||
console.log(`index.json sha256:${stats.indexSha}`)
|
||||
console.log(`临时仓库:${work}`)
|
||||
|
||||
git(['init', '-q', '-b', 'main'], work)
|
||||
cpSync(PACKS_DIR, work, { recursive: true })
|
||||
writeFileSync(join(work, 'README.md'), readme(stats, stamp))
|
||||
writeFileSync(join(work, '.gitattributes'), '*.png binary -diff\n*.json text eol=lf\n')
|
||||
git(['add', '-A'], work)
|
||||
git([
|
||||
'-c', 'user.name=d2web', '-c', 'user.email=d2web@localhost',
|
||||
'commit', '-q', '-m',
|
||||
`烘焙图集包 ${stamp}:${String(stats.maps)} 个地图块(${(stats.bytes / 1048576).toFixed(1)} MB)
|
||||
|
||||
本仓库刻意只保留一个提交:图集页是索引色 PNG,git 的 delta 压缩对它几乎无收益,
|
||||
每次重烘几乎全部文件都变;普通提交会让仓库每烘焙一次再长 ~60 MB。所以每次重烘都
|
||||
用 orphan 提交 + force push 覆盖(见 README)。
|
||||
|
||||
index.json sha256: ${stats.indexSha}
|
||||
|
||||
由 troytt/diablo2-web 的 npm run pack:data 生成。`,
|
||||
], work)
|
||||
console.log(`推送到 ${remote}(force,覆盖历史)…`)
|
||||
git(['push', '--force', remote, 'main'], work)
|
||||
console.log('完成。使用方:git clone ' + remote + ' samples/d2-packs')
|
||||
} finally {
|
||||
rmSync(work, { recursive: true, force: true })
|
||||
}
|
||||
Loading…
Reference in New Issue