From 0c0e1c73dbb25143d71dbaf20396ffbb4a08ac55 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 10 Oct 2026 01:04:05 +0200 Subject: [PATCH] Untouched-form walk: an empty Play opening is the table's refusal, not a route payload error The previous commit's walk expected acceptance; with no running turn the table refuses an empty opening, which is the proof the payload arrived. Builds. Co-Authored-By: Claude Opus 5.5 --- crates/ghostlight-dungeon/src/runtime.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/ghostlight-dungeon/src/runtime.rs b/crates/ghostlight-dungeon/src/runtime.rs index e9f163e..cd9c274 100644 --- a/crates/ghostlight-dungeon/src/runtime.rs +++ b/crates/ghostlight-dungeon/src/runtime.rs @@ -6431,17 +6431,26 @@ mod tests { let intent = untouched_intent(&surface, command, &typed); let sent = intent["payload"].clone(); let result = post(&fixture.state, &fixture.cookie, intent).await; - assert_eq!( - result["receipt"]["state"], "accepted", - "{command} with an untouched form ({sent}) was not admitted: {result}" - ); - exercised.push(command); if command == "world.play" { + // No turn is running here, so the empty box is refused by the + // play table itself (a new turn needs text). That refusal is + // the proof: the payload reached the table as `text: ""` + // instead of dying at the route as a missing field. Continuing + // a Running turn on that empty text is the table's own rule + // (`empty_text_while_the_turn_is_running_continues_it`). + assert_eq!(sent["bindings"]["text"], "", "{sent}"); + let message = result["receipt"]["message"].as_str().unwrap_or_default(); + assert!( + message.contains("a new turn needs non-empty text"), + "an untouched Play box did not reach the play table: {result}" + ); + } else { assert_eq!( - sent["bindings"]["text"], "", - "an untouched Play box must submit an empty continue" + result["receipt"]["state"], "accepted", + "{command} with an untouched form ({sent}) was not admitted: {result}" ); } + exercised.push(command); } // The walk reaches every button that captures a binding on the Active // surface, so a control added later without an authored value or a