固化 Match-3 生产者、视觉、音频与双 Judge 证据闭包。 将《山海行纪》r1.1 绑定新的不可变 release,并以生产预检现场核验 bundle、Registry/2 和 25 项 Writer 快照。 同步地图1平衡锁值、跨游戏回归修复、验收契约与 SoT 证据。
85 lines
4.2 KiB
Python
85 lines
4.2 KiB
Python
"""fresh Match-3 金标生成批次的静态边界测试。"""
|
||
|
||
import sys
|
||
import hashlib
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||
|
||
import match3_gold_batch as batch # noqa: E402
|
||
|
||
|
||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||
|
||
|
||
def test_candidates_are_five_distinct_fresh_games():
|
||
"""五款必须独立 gameId,不能把同一 fixture 的多轮冒充多款。"""
|
||
ids = [candidate["gameId"] for candidate in batch.CANDIDATES]
|
||
assert len(ids) == 5
|
||
assert len(set(ids)) == 5
|
||
assert all(game_id.startswith("gold-m3-") for game_id in ids)
|
||
assert ids == [
|
||
"gold-m3-gem-r3", "gold-m3-candy-r3", "gold-m3-porcelain-r3",
|
||
"gold-m3-fruit-r3", "gold-m3-rune-r3",
|
||
]
|
||
|
||
|
||
def test_candidates_keep_canonical_match3_requirements():
|
||
"""每份题面都必须明确 8×8、相邻交换、真实三连、终态和原生事件。"""
|
||
required = (
|
||
"8×8", "上下左右相邻", "无效交换必须换回", "20 步", "gameover",
|
||
"ctx.event", "puzzle.move-applied", "puzzle.swap-committed", "puzzle.match-formed",
|
||
"boardRevisionBefore", "boardRevisionAfter", "matchedCells", "matchedCount",
|
||
"puzzle.goal-reached", "puzzle.level-completed", "1-based row/column",
|
||
)
|
||
for candidate in batch.CANDIDATES:
|
||
assert all(fragment in candidate["brief"] for fragment in required)
|
||
assert candidate["brief"].count("_forensicsView") == 1
|
||
assert "通过 ctx.log 写原生 game 业务事件" not in candidate["brief"]
|
||
assert "禁止创建或启动 menu" in candidate["brief"]
|
||
assert "match3ProducerProfile" in candidate["brief"]
|
||
assert "drawSelectionOverlay" in candidate["brief"]
|
||
assert "第一次点选只更新 selected" in candidate["brief"]
|
||
assert "同一次 handleTap 同步调用栈" in candidate["brief"]
|
||
assert "全部确定性连锁、重力补新、计分和目标/通关判定" in candidate["brief"]
|
||
assert "这些业务事实不得延迟" in candidate["brief"]
|
||
assert "立即调用 match3VisualTimeline.begin(...) 封存视觉事务" in candidate["brief"]
|
||
assert "update(dt) 必须调用 match3VisualTimeline.update(dt)" in candidate["brief"]
|
||
assert "render(g) 必须读取 match3VisualTimeline.view().tiles" in candidate["brief"]
|
||
assert "禁止读取已经完成全部补位的业务终盘伪装动画" in candidate["brief"]
|
||
assert "业务终态可以同步 latch,但成功/失败场景只能在 timeline settle 后显示" in candidate["brief"]
|
||
assert "若耗尽最后一步,失败结果仍同步封存但延迟显示" in candidate["brief"]
|
||
|
||
|
||
def test_producer_api_exposes_machine_perceivable_theme_contract():
|
||
"""Writer 被要求先读的 API 必须写清图标感知约束,不能只给安全区坐标。"""
|
||
source = (REPO_ROOT / "game-runtime/src/plugins/match3-producer-profile/api.d.ts").read_text(
|
||
encoding="utf-8",
|
||
)
|
||
required = (
|
||
"覆盖格心的单个连续实心前景",
|
||
"禁止空心中心、分离装饰、文字和多块碎片",
|
||
"70..200 千分比",
|
||
"同一类别跨棋格逐像素保持同构",
|
||
"不叠加渐变、半透明柔光、随机纹理或位置相关高光",
|
||
)
|
||
assert all(fragment in source for fragment in required)
|
||
|
||
|
||
def test_generation_batch_never_uses_fixture_as_target():
|
||
"""生成批只允许从黄金 puzzle 起点生成 fresh ID,不得覆盖历史 match3 control。"""
|
||
assert batch.TEMPLATE == "_template-puzzle"
|
||
assert all(candidate["gameId"] != "match3" for candidate in batch.CANDIDATES)
|
||
assert batch.HARD_STOP_RMB == 75.0
|
||
|
||
|
||
def test_batch_manifest_keeps_full_briefs_and_hashes():
|
||
"""通用 brief 取证即使限长,合成金标批仍必须保留完整可复核原文。"""
|
||
manifest = batch._batch_manifest()
|
||
assert manifest["schemaVersion"] == "Match3GoldGenerationBatch/3"
|
||
assert len(manifest["candidates"]) == 5
|
||
for source, recorded in zip(batch.CANDIDATES, manifest["candidates"], strict=True):
|
||
assert recorded["brief"] == source["brief"]
|
||
assert len(recorded["brief"]) > 1000
|
||
assert recorded["briefSha256"] == hashlib.sha256(source["brief"].encode()).hexdigest()
|