kof-3d 在线试玩页的实现方法、工作原理,与「保留 github.io 多一跳 301」/「改成自定义域主链接」的实证对比
只读这一页也能拿走结论;后续每一节都是其中某条论断的展开与举证。
wangjunjian.com 归用户站点仓库所有,本仓库 cname=null;项目站点因此被 301 到自定义域子路径,全程零 DNS 改动。.github/workflows/pages.yml 驱动,每次 push 到 main 重建并部署;仓库只提交源码,可玩的单文件产物不入库。vite.config.ts 强制单 chunk,scripts/inline.mjs 做防御式内联,两者共同保证线上与 file:// 双击是同一个字节。InputRole 并按来源分桶,再进入与设备无关的纯逻辑层。↓ / ↘ / →+攻击 排成 3 个逻辑帧,复用与键盘手摇完全相同的 detectQcf() 判定链。github.io 的代价可量化:本机 8 次采样中,跨主机重定向使 num_connects 从 1 变 2,time_redirect 中位数 0.980s。github.io 是 GitHub 官方兜底的永久地址。建议主链接换域名、备链保留。num_connects,跨主机必须新建连接time_redirect 中位数,n=8把「打开一个链接」拆到底,会看到三个彼此独立的责任层。分层是理解后面所有实现细节的前提,也是排查线上问题时的定位顺序。
| 层 | 责任主体 | 关键文件 / 配置 | 失效时的典型症状 |
|---|---|---|---|
| 域名路由 | 用户站点仓库 + GitHub Pages 规则 | wang-junjian.github.io/pages 的 cname | 301 消失或目标域名变化,链接整体失效 |
| 构建发布 | 本仓库 Actions 工作流 | .github/workflows/pages.yml、vite.config.ts、scripts/inline.mjs | 页面 404、白屏、脚本被截断 |
| 浏览器运行 | 内联在单文件里的游戏代码 | src/main.ts、src/core/*、src/view/Controls.ts | 能打开但不能操作、设备按键无响应 |
这是最反直觉的一层,也是最容易被误认为「改了 DNS」的一层。事实是:本项目一行域名配置都没写。
GitHub Pages 有一条账户级规则:当用户站点仓库(wang-junjian/wang-junjian.github.io)绑定了自定义域,该账户下所有项目站点都会自动挂到这个自定义域的 /<仓库名>/ 路径下,同时从 <username>.github.io/<repo>/ 发出 301。所以只要在 kof-3d 仓库启用 Pages,内容就自动出现在 wangjunjian.com/kof-3d/。
github.io 与 wangjunjian.com 是不同主机,连接无法复用,因此多一次 DNS / TCP / TLS 往返。| 观测项 | 实际值 | 说明 | 证据强度 |
|---|---|---|---|
| 主站仓库 Pages | cname: wangjunjian.com, status: built | 自定义域归属在这里,不在 kof-3d 仓库 | 配置事实 |
| kof-3d 仓库 Pages | cname: null, build_type: workflow | 只启用 Actions 部署模式,未绑定任何域名 | 配置事实 |
| 重定向响应 | HTTP/2 301 → https://wangjunjian.com/kof-3d/ | server: GitHub.com | 实测 |
| 目标响应 | HTTP/2 200, content-length: 801938 | 内容仍由本仓库的 Pages artifact 提供 | 实测 |
wangjunjian.com/kof-3d/ 会直接失效,而 github.io/kof-3d/ 仍由 GitHub 兜底。这是第 06 节对比里最容易被忽略的一项。发布链路解决「内容从哪来」,构建链路解决「为什么内容可以只有一个文件」。后者是这套方案能同时支持在线试玩与离线双击的关键。
npm run build 展开是三步:tsc --noEmit → vite build → node scripts/inline.mjs。vite.config.ts 用 inlineDynamicImports: true、cssCodeSplit: false、assetsInlineLimit: 100_000_000 和固定 entryFileNames: 'game.js',把 Three.js 与全部业务代码压成恰好一个 JS chunk 与最多一个 CSS chunk;base: './' 保证产物里不出现绝对路径。
assets/character.vrm 以 base64 内联,因此体积约 3.8 MB,不占 < 1 MB 的默认版约束。| 细节 | 做法 | 不这么做的后果 |
|---|---|---|
| 脚本类型 | 内联为普通 <script>,不是 module | file:// 下 module 触发 CORS,离线双击直接白屏 |
| 插入位置 | 放在 </body> 之前 | 普通 script 没有 module 的 defer 语义,DOM 尚未解析完就取不到 HUD 节点 |
| 转义与剥离 | 转义 </script / </style、替换 import.meta、删除 preload / modulepreload | 压缩产物里的字符串会提前闭合标签,或残留对 ./game.js 的请求 |
| 多 chunk 防御 | JS chunk ≠ 1 时直接抛错 | 旧实现会静默用最后一个 chunk 覆盖前面的,产出「能打开但脚本残缺」的坏文件 |
.github/workflows/pages.yml 在 push 到 main(或手动 workflow_dispatch)时触发两个 job:build 跑 npm ci → test → typecheck → build,再组装 site/——把单文件复制成 index.html 与 kof-3d.html,加 .nojekyll 阻止 Jekyll 处理,用 build.txt 记录 $GITHUB_SHA 供线上溯源;deploy 调官方 deploy-pages@v4。permissions 只授 contents: read / pages: write / id-token: write,concurrency 用 cancel-in-progress: false 避免两次部署互相踩出站点空档。
CI 只跑纯逻辑层,因为 npm run verify 依赖本机 Chrome;浏览器端到端断言留在本地验收。这是有意的能力边界,不是遗漏。
单文件加载后,window.__kof.ready 置位即代表 Three.js 场景、帧表与 HUD 都已就绪。之后所有玩法都跑在一个与设备无关的纯逻辑层上。
src/main.ts:245 的 FixedLoop 把逻辑固定在 60Hz:每个逻辑帧先 controls.sync()(Gamepad 是轮询设备,没有 DOM 事件,必须在采样前汇入),再 match.step(input.sample());渲染回调按 alpha 插值更新姿态、特效与镜头。因此浏览器帧率波动不会改变判定结果,顿帧(hitstop)冻结逻辑时视觉时间也一起冻结。
core/input.ts 用 Map<InputSource, Set<InputRole>> 保存按住状态,roleHeld() 只要任一来源持有即为真。手柄每帧 press/release 才不会误清键盘按住的方向。view/Controls.ts 用 setPointerCapture 配合 pointerup / pointercancel / lostpointercapture 三处抬键,手指滑出按钮也不会留下一个永远按着的攻击键。queueQcf() 排出的三帧依次进出 Match 的 20 帧历史,第三帧触发 detectQcf();返回的绝对方向由 Fighter 与 facing 比对,输入层完全不掺角色状态。这一节把前面所有断言压成可执行的观测。证据强度按「配置事实 / 实测 / 代码事实 / 推断」标注,实测数据注明样本量与口径。
| 结论 | 复跑命令 | 观测值 | 强度 |
|---|---|---|---|
| 产物只提交源码 | git ls-files kof-3d.html | 无输出;.gitignore 忽略两份单文件与 shots/ | 配置事实 |
| 单文件零外部引用 | grep -oE '(src|href)="[^"]*"' kof-3d.html | 无外部资源;<script> 与 <style> 各 1 个 | 实测 |
| 线上与本地同一字节 | shasum -a 256 kof-3d.html 对比 curl -sSL …/index.html | shasum -a 256 | 两者均为 0c9183d2…0fd16 | 实测 |
| 部署溯源 | curl -sSL https://wangjunjian.com/kof-3d/build.txt | Playable build of commit 25b3a86c… | 实测 |
| 两层断言全绿 | npm test / npm run verify | 67 / 67 单测、97 / 97 浏览器断言,连跑 5 次 | 实测 |
| 线上零报错、零外部请求 | 本地 Chrome 打开线上 URL 抓 pageerror / console.error / requestfailed | 三项均为 0,__kof.ready 正常 | 实测 |
| Actions 部署成功 | gh run view 42140549807 | success,build 15s、deploy 9s | 实测 |
| 指令识别窗口单一来源 | 读 src/core/input.ts / src/core/match.ts | QCF_WINDOW = 20,仅 Match 维护历史 | 代码事实 |
num_connects、content-length、SHA256 这类计数与内容类观测不受网络抖动影响。问题收窄成一句话:README 与仓库 homepage 继续写 github.io(方案 A,现状),还是改成 wangjunjian.com/kof-3d/(方案 B)。两者页面内容完全相同,差别只在访客抵达内容之前的那段路。
现状。README 顶部与 homepage 都指向 https://wang-junjian.github.io/kof-3d/,浏览器收到 301 后跳到自定义域。可用,但每次首访多一次跨主机往返。
把这两处文本改成 https://wangjunjian.com/kof-3d/,访客一步直达 200。不改 DNS、不动主站仓库、不碰构建脚本。
x = 220 + 194.3 × 秒数),两行共用刻度,可直接比长度。time_redirect = 0.980s 是 curl 直接给出的重定向阶段耗时,比两次独立采样的 total 差值更纯粹。| 指标 | A · github.io(跟随 301) | B · 自定义域直达 | 口径与强度 |
|---|---|---|---|
num_redirects | 1 | 0 | 计数实测 |
num_connects | 2 | 1 | 计数实测 |
time_appconnect 中位数 | 0.945s | 0.567s | n=8,本机 |
time_redirect 中位数 | 0.980s | 不适用 | n=8,本机 |
time_total 中位数 | 3.279s | 2.788s | n=8,波动大 |
| 响应内容 | 同一份 index.html,801938 B,SHA256 0c9183d2… | 内容实测 | |
采样区间:A 的 total 最小 2.339s / 最大 4.056s,B 的最小 1.844s / 最大 5.274s。两组分布明显重叠,因此「B 一定更快」这个说法证据不足;能站得住的结论是「B 少一次连接、少一次 TLS、少一段确定的重定向阶段耗时」。
| 维度 | A · 保留 github.io | B · 改成主链接 | 谁更好 |
|---|---|---|---|
| 首访往返 | 1 次 301 + 跨主机新连接 + 第二次 TLS | 直接 200,单连接 | B |
| 回访差异 | 301 可被浏览器缓存,但跨主机连接仍无法复用;差距缩小但不归零 | B 略优 | |
| 品牌与信任 | 显示 github.io 子域,像演示页 | 自有域名,像正式产品 | B |
| 分享预览 / SEO | 301 权重传递基本无损,但预览卡片可能展示原始 github.io 地址 | 预览即最终地址 | B |
| 链接寿命与依赖 | GitHub 官方地址,本项目自己可控 | 依赖主站仓库的 cname 配置,域名一变全断 | A |
| fork / 迁移友好度 | 改用户名即可推导新地址 | 硬编码个人域名,fork 后指向原站 | A |
| 改动成本 | 零改动(现状) | 2 处文本:README 第 5 行 + gh repo edit --homepage | A 略优 |
| 失效模式 | Pages 关闭才失效,与域名无关 | 主站换域 / 自定义域验证失效 / Pages 关闭,任一即断 | A |
wangjunjian.com 的 cname 配置不在本仓库,把所有对外链接都押在它上面,等于把项目的可访问性外包给另一个仓库的长期维护状态。先明确标注:以下是观点,不是共识。数据与配置事实在前一节,判断建立在这些事实与你的使用场景之上。
不是二选一。对外只主推 https://wangjunjian.com/kof-3d/(更快、更体面、少一跳),但在 README 里以「备用地址 / permalink」的形式留一行 github.io 链接。这样既拿到 B 的体验与观感,又保留 A 的永久性与 fork 友好度;万一主站域名变更,读者仍能顺着备用地址找到内容。
如果更看重「链接十年后还能打开」而不是首访那不到一秒,反过来把 github.io 留作主链接也完全站得住——它是 GitHub 官方兜底,且 301 会被缓存,代价主要在首次访问。
README.md 第 5 行「在线试玩 / Play online」的 URL 改成 https://wangjunjian.com/kof-3d/。gh repo edit wang-junjian/kof-3d --homepage "https://wangjunjian.com/kof-3d/" 更新仓库主页链接。https://wang-junjian.github.io/kof-3d/,注明会自动跳转。push: branches: [main],没有路径过滤。改 README 并 push 会触发一次完整构建与部署,产物内容不变但 build.txt 的 commit 会更新。这是预期行为,不是故障;若不想触发,可在工作流加 paths-ignore: ['**.md'],但那样又会让文档变更失去一次 CI 校验,需自行权衡。# 1) 量 301 的净开销(8 次采样,比较 num_connects 与 time_redirect)
for i in $(seq 1 8); do
curl -sSL -o /dev/null -w '%{num_connects} %{time_redirect} %{time_total}\n' \
https://wang-junjian.github.io/kof-3d/
done
# 2) 直达对照
for i in $(seq 1 8); do
curl -sS -o /dev/null -w '%{num_connects} %{time_total}\n' \
https://wangjunjian.com/kof-3d/
done
# 3) 确认两种入口拿到同一份产物
shasum -a 256 kof-3d.html
curl -sSL https://wang-junjian.github.io/kof-3d/index.html | shasum -a 256
curl -sSL https://wangjunjian.com/kof-3d/build.txt
# 4) 本地两层验收(按 README 固定顺序)
npm test && npm run typecheck && npm run build && npm run verify
竖屏手机目前只给提示、不提供完整竖屏布局;手柄键位写死、无重绑定与振动;AI 不放必杀;无选人界面;M3 的美术打磨与联机未解禁。这些与链接选型无关,列在这里避免把「能打开」误读成「已完工」。