Skip to content

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
mainfrom
feat/shortcut-wrong-side-hint
Open

fix(compiler): a click trigger gets the body of the object at its anchor, not a point (task #50)#324
stellarfeline wants to merge 1 commit into
mainfrom
feat/shortcut-wrong-side-hint

Conversation

@stellarfeline

Copy link
Copy Markdown
Owner

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-shortcut fixture's gate anchor. On origin/main it compiles with zero diagnostics and emits:

summon minecraft:interaction 4.5 65.0 6.5 {width:1.0f,height:2.0f,response:1b,Invulnerable:1b,Tags:["dw_trig_…"]}

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 rule SEAL_MARGIN exists 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/main and against this branch:

# origin/main (3ad87e9)
body [4.0, 65.0, 6.0]..[5.0, 67.0, 7.0] is sealed inside the door
     [4.0, 65.0, 6.0]..[6.0, 68.0, 7.0] and no press can reach it
every_cell_of_the_doorway_answers: left: 1, right: 6
the_door_answers_only_from_the_sealed_side: [(4.5, 65.0, 6.5, 1.0, 2.0)]
test result: FAILED. 3 passed; 8 failed

# this branch
test result: ok. 11 passed; 0 failed

a_point_anchor_is_untouched passes on both — that is the byte-identity evidence for triggers anchored in open air.

The change

compiler::pressable is the single authority for what a click lands on at an anchor. The emitter and compiler::eclipse both 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:

  • ride an existing compiler-owned set where one covers the anchor (a close-gate seal, a sealed shortcut door) — one cell, one hitbox; a second co-located box is the DW0422 ray-pick tie that killed the island's boulder;
  • region — one 1.02f protruding box per clickable shell cell, exactly what close-gate has done privately since v0.8;
  • point — the ordinary 1.0f × 2.0f body, unchanged.

close-gate already 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/dsl is 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 the unlock level with the doorway). Withhold, never invent. Raised before the route proofs so an undecidable doorway is not misreported under DW0374'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-gate seal — 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_entity cannot 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 measured SEAL_MARGIN finding, and asserted here as geometry.

Scope held

  • close-gate.sealed_hint is 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.
  • Feature-bound fields spotted in passing and forwarded to the audit worker rather than touched: bonfire.prompt / rest_label / save_label, interact.missing_item_hint, collect.item_name, dialogue-option tooltip.
  • The fixture adopts the capability (a strike trigger 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, and tools/check-dw-codes.py (209 codes, bidirectional, all test-covered) are green. docs/reference/compiler.md carries the two new codes, the trigger-body behaviour and the shortcut-door row in the same PR.

…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant