fix(compiler): a click trigger gets the body of the object at its anchor, not a point (task #50) - #324
Open
stellarfeline wants to merge 1 commit into
Open
fix(compiler): a click trigger gets the body of the object at its anchor, not a point (task #50)#324stellarfeline wants to merge 1 commit into
stellarfeline wants to merge 1 commit into
Conversation
…hor, not a point (task #50) `EnvTrigger` is already the campaign's general "click a thing, run anything" verb — any anchor, both clicks, the full effect vocabulary, flag gates, `once`. Nothing was missing at the response layer. What was missing is underneath it: the trigger's body is a point at a cell, and an object in the scene is a shape. Measured on the `souls-shortcut` fixture, a `use` trigger on the shortcut's gate emitted one body with AABB [4,65,6]..[5,67,7] inside a doorway slab occupying [4,65,6]..[6,68,7] — flush with the block on the faces it touches, interior on the rest. Vanilla bounds its entity raycast by the block hit and takes the entity only when it is strictly nearer, so that trigger was pressable from no angle at all, with zero diagnostics. A doorway is also six cells; a point body covers one. `close-gate` had solved exactly this privately inside one verb since v0.8 (shell cells plus SEAL_MARGIN) and nothing else could reach the machinery — which is why the same press works on a sealed boulder and not on a barred shortcut door. `compiler::pressable` is now the single authority for what a click lands on at an anchor. Both the emitter and `compiler::eclipse` read it, so they can no longer disagree about whether a body exists. Three outcomes: ride an existing compiler-owned set where one covers the anchor (one cell, one hitbox — a second co-located box is the DW0422 ray-pick tie); arm the region's clickable shell where the anchor names a region; the ordinary point body in open air, unchanged. A sealed shortcut door's bodies stand in the open air on the sealed side only (`compiler::wrongside`), which is the whole side mechanism and needs no player test — a near-side ray reaches the body before the block, a far-side ray hits the door and stops. That matters because the answer is typically "the door cannot be opened from this side", which said on the opening side is false, and a false player-facing line is worse than silence. Two diagnostics: - DW0426, the unbound-vacuity class as a check: a click trigger anchored where nothing is clickable. This is the rule that would have caught the gap. - DW0425: the compiler will not guess which side of a doorway is sealed. No DSL surface is added — `crates/dsl` is untouched. `close-gate.sealed_hint` is deliberately not modified; its duplication of this mechanism goes to the audit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this turned out to be
The task began as "a shortcut door needs a wrong-side hint". Checking that against the code inverted it twice, and the finding is better than the feature would have been.
EnvTrigger—{at, on: strike|use, effects: Vec<QuestEffect>, requires_flags, forbids_flags, once}— already is the general "click a thing, run anything" capability: any anchor, both clicks, the full effect vocabulary, prose or trap or command. Nothing was missing at the response layer, and a new field would have been a third mechanism for one behaviour, strictly weaker than the one that exists.The defect is one layer down: a trigger's body is a point at a cell, and an object in the scene is a shape.
The red I watched happen
Author the island boulder's shape — a repeatable click trigger carrying the author's prose and a sound — on the
souls-shortcutfixture's gate anchor. Onorigin/mainit compiles with zero diagnostics and emits:That body's AABB is
[4.0,65.0,6.0]..[5.0,67.0,7.0]. The doorway slab it stands in occupies[4.0,65.0,6.0]..[6.0,68.0,7.0]. Every axis is contained; the z faces are flush at both 6.0 and 7.0. Vanilla bounds its entity raycast by the block hit distance and takes the entity only when it is strictly nearer — the ruleSEAL_MARGINexists to satisfy (emit.rs:4781-4790, measured). So the press reaches nothing from any angle, and a doorway is six cells of which this covers one.The same test file, unmodified, against
origin/mainand against this branch:a_point_anchor_is_untouchedpasses on both — that is the byte-identity evidence for triggers anchored in open air.The change
compiler::pressableis the single authority for what a click lands on at an anchor. The emitter andcompiler::eclipseboth read it, so they can no longer disagree about whether a body exists — a disagreement invisible in the DSL that only shows up as a dead click in a playtest. Three outcomes:close-gateseal, a sealed shortcut door) — one cell, one hitbox; a second co-located box is theDW0422ray-pick tie that killed the island's boulder;1.02fprotruding box per clickable shell cell, exactly whatclose-gatehas done privately since v0.8;1.0f × 2.0fbody, unchanged.close-gatealready had the region machinery locked inside one verb. This lifts the question out, so the general mechanism starts working on objects where it silently did not — everywhere at once, with no new DSL surface:crates/dslis untouched.Because
mark(task #38) declares an anchor anywhere, "any block in the scene" follows for free.Sidedness
A sealed shortcut door's bodies stand in the open air on the sealed side only. That placement is the side test and needs no player test at all: a near-side ray reaches the body before the block, a far-side ray hits the door and stops.
This is required, not decorative. The owner's line is "The door cannot be opened from this side." Said to a player standing where it does open, that sentence is false — and a false player-facing statement is worse than silence, because silence teaches nothing and a lie teaches something wrong. A trigger is dispatched from the tick under the server command source and never knows who pressed it, so a position test on the player was never available; geometry was the only mechanism, and it is the better one.
Diagnostics
DW0426— a click trigger anchored where nothing is clickable. The unbound-vacuity class as a check, and the rule that would have caught this gap: anchor, click and effects all declared, emission runs, press lands on nothing, every board green.DW0425— the compiler will not guess which side of a doorway is sealed (no unique thin axis, or theunlocklevel with the doorway). Withhold, never invent. Raised before the route proofs so an undecidable doorway is not misreported underDW0374's name.Binding count — read this before reading the green board
0 sealed shortcuts exist on disk. No campaign in the content repo declares
shortcuts[]; the drowned-bell remake will be the first. Every proof in this PR is therefore unbound until that campaign exists. A green board here proves the machinery is internally consistent — it does not prove a player pressing bars in a delve gets an answer. Nobody should later read this green as coverage.What is bound, in the fixture (
souls-shortcut): 1 shortcut, 1 gate anchor, 6 doorway cells, 6 bodies, 1 riding trigger.Across the four campaigns on disk, the pressable objects that exist today are 16: 7 NPC dialogue hitboxes, 7 interact objectives, 1 click trigger, 1
close-gateseal — against 1 region-anchored object. That ratio is the clearest evidence for why this was never noticed: almost everything authored so far is a point in open air, where the point body is correct.The half that stays unproven by machine either way:
player_interacted_with_entitycannot be fired by PackTest (it needs a real client right-click,emit.rs:4933-4936), so ray-pick reachability is argued from vanilla's documented pick rule and the codebase's own measuredSEAL_MARGINfinding, and asserted here as geometry.Scope held
close-gate.sealed_hintis not modified and its default is not deleted. It is the prior art for this mechanism and its private duplication of it goes to the audit, not here.bonfire.prompt/rest_label/save_label,interact.missing_item_hint,collect.item_name, dialogue-optiontooltip.striketrigger on its gate carrying the author's own prose and sound) — that is what binds the new machinery to a real campaign shape.What CI now proves
cargo test --workspace,clippy -D warnings,fmt --check, andtools/check-dw-codes.py(209 codes, bidirectional, all test-covered) are green.docs/reference/compiler.mdcarries the two new codes, the trigger-body behaviour and the shortcut-door row in the same PR.