AgentDock

Trigger Integrity And Onscreen Gate

Archived on 2026-08-20 · roadmap: game-web-phaser-template

Archived: 2026-08-20 · roadmap-id: game-web-phaser-template

Why

一句话

断言的触发方式,使它对被验对象的损坏免疫。

真实经过

那一版游戏里旗子(终点)与刺一直往下掉、掉出画面。直接根因在游戏代码:

this.goal = this.physics.add.sprite(...)
this.goal.setImmovable(true)     // ← Arcade 里 immovable 不关重力,只表示"不会被碰撞推动"

要静止得 setAllowGravity(false) 或用 staticSprite;刺同理用了动态的 physics.add.group(), 而地面用的是 staticGroup()(正确)。全文件只有一处 staticGroup、零处 setAllowGravity。

但这一刀不修那个游戏。 要修的是下面这件事:

registerTrigger('level_advance', () => this.simulateReachGoal())
// simulateReachGoal() → this.player.setPosition(this.goal.x, this.goal.y)

它把玩家传送到旗子的当前位置。旗子掉到哪,玩家就被传送到哪——overlap 照常触发、 分数照常变、score_feedback 照常绿。断言与被验对象共同移动,因此永远不会红。

🔴 这条规矩本来就有,它是靠人工 review 兜的,然后它失效了

templates/game-web-phaser/AGENTS.md:70 逐字写着:

The handler may only do what a real player's own actions could cause — spawn something in the world and let the existing overlap/collision logic react … This one line is enforced by human review, not by the type system; do not treat "the linter didn't complain" as permission.

模板参考实现做的是往玩家脚下生成一枚硬币(GameScene.ts:261 spawnCoinAtPlayer),让既有 overlap 逻辑自己反应。执行者写的是反过来的:把玩家搬到目标处。它没违反字面(没直接写 score),却破坏了意图。

所以本刀的形态不是"补一条规矩",是把一条已被实测证明会失效的散文纪律挪进代码层—— 与 cogito-lib backlog 条目 13 同形态,也与该仓库既有的三处做对了的先例同形态 (scaffold-probe.ts / git-identity.ts / artifact-port.ts 都把判断从散文挪成了平台执行的探测)。

影响面不是这一版游戏

凡是用「registerTrigger + 把玩家传送到目标处」这种模式实现的断言,都有同样的免疫性—— 它绕过了"玩家能不能真的走到那里"这个真问题。而模板文档鼓励的正是 registerTrigger 这套机制。

Non-goals

  • ❌ 不修「用话造关」那一版游戏。存量生成项目不会自动拿到模板改动(发布链路是 changeset → Version Packages PR → npm → 下一次脚手架)。这一点如实记在这里,不假装它被修了。
  • ❌ 不改断言模板集合(仍是上游 7 个),不新增 templateId。
  • ❌ 不给 A/B 任何逃生阀(环境变量开关、skip 字段)。能被关掉的闸不是闸。
  • ❌ 不动 press() / applyState() 的既有契约,不新增任何 setter 形状的 harness 方法。

On this page