lili cd292b2d65 feat(cheap-worker): TRPG 品类黄金脚手架 _template-trpg(W-GENRE 品类件②)
per-genre 轻起点骨架(照 _template-shop/_template-story 打样、L1 六件与 _template 逐字同):
勇者限时闯塔→每层翻遭遇卡(怪物/宝箱/奇遇,难度与奖励亮牌)→点卡=掷 d20+等级加成
vs 难度三档结算(金币/经验升级/怪物卡失败扣血)→清层登塔难度渐升→登顶 win/HP 耗尽
lose/60s 到点结算。掷骰过程可见(飘判定式 d20:骰+加成 vs 难度——不黑箱)。
_forensicsView 的 targets/score 取证契约 = 九门 tap-targets occupied driver 可达
(play 态 ready 遭遇卡 occupied:true、menu 空数组防误点;score=金币战利品单调升,
失败也有安慰 1 金=进展契约;卡命中区=绘制区=取证 targets 三者同源于 core.buildFloor)。
core.js 顶部多样性参数空间(ENCOUNTER_DECK 遭遇表/层数/血量/难度经验曲线)+
结构约束单测机器化(牌库 ≥4 项三 kind 齐/同层无重复抽样/难度地板 2)。

前任半成品盘点续做:src/ 7 件 + entry/index/manifest 已完整(沿用,零改动);
本次补齐 README.md + test/core.test.mjs + test/headless-boot.test.mjs + assets/gen-ledger.md。

验收:core 单测 13/13 + headless 全链路 3/3(真 tap 坐标起局→targets 驱动逐卡掷骰
→score 严格递增→翻层→over latch→再闯一次;同 seed 确定性;异 seed 牌面多样);
scaffold-saa/check/build-saa/stage/smoke 全 PASS(check=API 静态门+形状门对范例签名过门);
独立过九门 9/9(verdict: games/_wg1-gen/p11c-tpl/evidence/verdict.json,
H_progress score 0→97 increased + latch over 驻留,F_wiring sessionScore./audioMusic./juice. 全中);
金标 spec 注入复验九门同样 9/9。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:31:05 -07:00

101 lines
4.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!--
index.html — 「点圆得分」(_template 克隆起点)宿主页
用法:
· 构建产物(iife 全局 TemplateGame):见 entry.js 头注释的 build.mjs 锁参命令(产物 dist/template-bundle.js);
· serve 本目录,浏览器打开;
· ?seed=12345 注入主随机种子(缺省固定种子,确定可复现);
· ?engine=stub(缺省·回滚锚)/ ?engine=real(引擎掌帧);
· ?mode=evidence 取证模式:stub 下不起 RAF,由 window.__gameHost.stepFrames/tap 确定性驱动;
· 取证导出:window.__gameForensics / window.__gameHost;就绪标记:__gameBooted。
注意:页面壳只负责承载;游戏全部绘制在 canvas 内。
-->
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<!-- 固定取证视口:390×844 移动竖屏基准;禁缩放防像素哈希扰动。 -->
<meta name="viewport" content="width=390, height=844, initial-scale=1, user-scalable=no" />
<title>点圆得分</title>
<style>
/* 自适应居中画布:游戏全部绘制在 canvas 内,页面壳只承载。
画布按 390:844 比例缩放 fit 窗口——桌面短窗口不裁底(否则底部内容如按钮/角色看不见)。
host 输入映射按 getBoundingClientRect 归一化(boot-game-host toCanvasXY),CSS 缩放下点击坐标仍准。 */
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #0a1622;
color: #9fb3c8;
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
overflow: hidden;
}
#wrap {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
#game {
/* 取「宽受限」与「高受限」的较小者,保 390:844 比例完整可见。
!important 覆盖 host 在 stub 通道写的内联 display(390×844 硬编码);buffer 不动→evidence 像素哈希不受影响;
输入经 boot-game-host toCanvasXY 按 getBoundingClientRect 归一,CSS 缩放下点击坐标仍准。 */
width: min(100vw, calc(100vh * 390 / 844)) !important;
height: min(100vh, calc(100vw * 844 / 390)) !important;
background: #10243a;
display: block;
touch-action: none; /* 触摸全交 JS(pointer 桥接),禁浏览器默认手势 */
}
#status {
position: absolute; /* 浮层叠于 canvas 顶部,不占布局流 */
top: 0; left: 0; right: 0;
z-index: 2;
pointer-events: none; /* 绝不截触摸 */
font-size: 12px;
line-height: 1.4;
min-height: 15px;
text-align: center;
word-break: break-all;
opacity: 0.7;
}
</style>
</head>
<body>
<div id="wrap">
<div id="status">booting…</div>
<canvas id="game"></canvas>
</div>
<!-- 构建产物(iife 全局 TemplateGame,含 bootGame)。克隆后按 entry.js 头注释构建生成。 -->
<script src="./dist/template-bundle.js"></script>
<script>
// 引导:异步 boot;失败把错误打到 status + 重抛(评估门要看 console 零未捕获错误)。
(function () {
'use strict';
var statusEl = document.getElementById('status');
var canvas = document.getElementById('game');
try {
var G = window.TemplateGame;
if (!G || typeof G.bootGame !== 'function') {
throw new Error('TemplateGame.bootGame 未找到(template-bundle.js 未正确加载或 globalName 不符)');
}
G.bootGame({ canvas: canvas, statusEl: statusEl })
.then(function () { window.__gameBooted = true; })
.catch(function (e) {
window.__gameBooted = false;
window.__gameBootError = String((e && e.message) || e);
if (statusEl) statusEl.textContent = 'BOOT ERROR: ' + window.__gameBootError;
throw e;
});
} catch (e) {
window.__gameBooted = false;
window.__gameBootError = String((e && e.message) || e);
if (statusEl) statusEl.textContent = 'BOOT ERROR: ' + window.__gameBootError;
throw e;
}
})();
</script>
</body>
</html>