From 8af8962330ca16b37171d5ea97b24088dd2321df Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sun, 16 Aug 2026 15:20:46 -0400 Subject: [PATCH] =?UTF-8?q?Warp=20sight=20honors=20"as=20though=20they=20w?= =?UTF-8?q?ere=20adjacent"=20=E2=80=94=20diagonals=20included?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Playtesting caught the gap with a sticky wand: a target visible through the opening but one square off the corridor axis was refused. The colinear-only model was stricter than the rulebook, whose actual instruction is to treat the connected edges as adjacent boards with a straight center-to-center line — and a straight line through a one-cell opening may run diagonally, exactly as through a doorway. hasWarpLineOfSight now abuts the far side virtually at the mouth (rotating when the pairing turns a corner, as aisle warps do), finds where the center-to-center line crosses the rim, requires that crossing to fall strictly within the open mouth (corner-grazing stays blocked — the strict reading), and checks the two legs against real walls on each side of the seam. hasLineOfSight's core became a shared segmentClear to serve both legs. The colinear corridor cases remain as the special case they always were; a new test pins that off-axis squares are visible through mouths. Co-Authored-By: Claude Fable 5 --- .../console-2026-08-16T19-04-55-807Z.log | 98 +++++++++++++++++ packages/engine/src/board.ts | 101 +++++++++++++----- packages/engine/test/board.test.ts | 27 +++++ 3 files changed, 198 insertions(+), 28 deletions(-) diff --git a/.playwright-mcp/console-2026-08-16T19-04-55-807Z.log b/.playwright-mcp/console-2026-08-16T19-04-55-807Z.log index 70a266e..806163c 100644 --- a/.playwright-mcp/console-2026-08-16T19-04-55-807Z.log +++ b/.playwright-mcp/console-2026-08-16T19-04-55-807Z.log @@ -140,3 +140,101 @@ [ 555769ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 [ 557346ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 [ 558933ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 560699ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 562433ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 564351ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 566850ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 571504ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 576704ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 580031ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 586469ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 591484ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 597562ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 601328ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 604230ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 610716ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 614756ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 619191ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 622632ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 628757ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 633165ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 635772ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 641787ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 646319ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 650540ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 653919ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 659958ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 663089ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 664597ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 666107ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 667620ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 669125ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 670630ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 672135ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 673660ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 675188ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 676720ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 678303ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 679929ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 681713ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 683638ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 686042ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 688483ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 694213ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 700120ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 705030ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 708102ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 713737ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 718257ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 723483ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 726575ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 731009ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 733615ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 739850ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 743296ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 747591ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 750935ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 753792ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 759827ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 765846ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 770080ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 776003ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 781143ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 785813ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 790908ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 794723ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 799978ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 802566ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 808697ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 812967ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 817030ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 820028ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 826526ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 829216ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 833505ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 838637ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 843812ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 849871ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 854886ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 860741ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 865567ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 869130ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 874426ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 880608ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 885176ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 888150ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 891310ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 894480ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 899982ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 904792ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 908807ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 914072ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 916853ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 920114ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 925647ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 930995ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 934357ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 937913ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 941634ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 947338ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 950320ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 diff --git a/packages/engine/src/board.ts b/packages/engine/src/board.ts index 16547a9..cf8f5db 100644 --- a/packages/engine/src/board.ts +++ b/packages/engine/src/board.ts @@ -268,14 +268,23 @@ export function hasLineOfSight( blockedCells?: Record, ): boolean { if (cellKey(from) === cellKey(to)) return true; - // Centers of cells: (x + 0.5, y + 0.5). - const x0 = from.x + 0.5, y0 = from.y + 0.5; - const x1 = to.x + 0.5, y1 = to.y + 0.5; + return segmentClear( + board, from.x + 0.5, from.y + 0.5, to.x + 0.5, to.y + 0.5, + blockedCells, [cellKey(from), cellKey(to)], + ); +} +/** Is this raw sight segment unobstructed by walls or solid cells? */ +function segmentClear( + board: AssembledBoard, + x0: number, y0: number, x1: number, y1: number, + blockedCells: Record | undefined, + skipCells: string[], +): boolean { if (blockedCells) { for (const key of Object.keys(blockedCells)) { + if (skipCells.includes(key)) continue; const [bx, by] = key.split(",").map(Number) as [number, number]; - if ((bx === from.x && by === from.y) || (bx === to.x && by === to.y)) continue; // The sight line is blocked if it crosses any side of the solid cell. const sides: [number, number, number, number][] = [ [bx, by, bx + 1, by], @@ -334,12 +343,14 @@ function onSegment(ax: number, ay: number, bx: number, by: number, px: number, p } /** - * Straight-corridor sight through a wraparound opening. Rulebook: "If - * casting a spell, or checking line of sight through the AUTO WARP, treat - * it as a straight line, and the two connected boards as though they were - * adjacent" — and the lettered openings reconnect board edges the same way - * ("you will reenter at point A"). The line must run down the corridor, out - * one mouth, and straight in through the paired mouth: no diagonals. + * Sight through a wraparound opening. Rulebook: "If casting a spell, or + * checking line of sight through the AUTO WARP, treat it as a straight + * line, and the two connected boards as though they were adjacent" — and + * the lettered openings reconnect edges the same way. So the far side is + * virtually abutted at the mouth (rotated if the pairing turns a corner, + * as the aisle warps do), and the center-to-center line must pass through + * the one-cell opening: diagonals through the gap are as legal as they are + * through any doorway. */ export function hasWarpLineOfSight( board: AssembledBoard, @@ -347,31 +358,65 @@ export function hasWarpLineOfSight( to: Cell, blockedCells?: Record, ): boolean { + const DIR: Record = { + N: { x: 0, y: -1 }, S: { x: 0, y: 1 }, E: { x: 1, y: 0 }, W: { x: -1, y: 0 }, + }; + const EPS = 1e-9; for (const w of board.warps) { const mouthA = w.from.cell; - const out = w.from.side; + const sideA = w.from.side; const mouthB = w.to.cell; const inward = opposite(w.to.side); - // The viewer stands in mouth A's corridor, on the interior side. - const fromAligned = out === "N" || out === "S" ? from.x === mouthA.x : from.y === mouthA.y; - if (!fromAligned) continue; - const fromInterior = - out === "N" ? from.y >= mouthA.y : out === "S" ? from.y <= mouthA.y : - out === "E" ? from.x <= mouthA.x : from.x >= mouthA.x; - if (!fromInterior) continue; - // The target sits in mouth B's corridor, from the mouth inward. - const toAligned = inward === "N" || inward === "S" ? to.x === mouthB.x : to.y === mouthB.y; - if (!toAligned) continue; - const toInterior = - inward === "N" ? to.y <= mouthB.y : inward === "S" ? to.y >= mouthB.y : - inward === "E" ? to.x >= mouthB.x : to.x <= mouthB.x; - if (!toInterior) continue; - // A filled mouth blocks the tunnel unless the viewer/target IS the mouth. + // A filled mouth chokes the tunnel unless the viewer/target IS the mouth. if (blockedCells?.[cellKey(mouthA)] && cellKey(from) !== cellKey(mouthA)) continue; if (blockedCells?.[cellKey(mouthB)] && cellKey(to) !== cellKey(mouthB)) continue; + + // Rotation taking the far board's inward direction onto sideA. + const u = DIR[inward], v = DIR[sideA]; + const cos = u.x * v.x + u.y * v.y; + const sin = u.x * v.y - u.y * v.x; + const centerB = { x: mouthB.x + 0.5, y: mouthB.y + 0.5 }; + const nA = neighbor(mouthA, sideA); + const anchor = { x: nA.x + 0.5, y: nA.y + 0.5 }; + const T = (p: { x: number; y: number }) => ({ + x: cos * (p.x - centerB.x) - sin * (p.y - centerB.y) + anchor.x, + y: sin * (p.x - centerB.x) + cos * (p.y - centerB.y) + anchor.y, + }); + const Tinv = (p: { x: number; y: number }) => ({ + x: cos * (p.x - anchor.x) + sin * (p.y - anchor.y) + centerB.x, + y: -sin * (p.x - anchor.x) + cos * (p.y - anchor.y) + centerB.y, + }); + + const c0 = { x: from.x + 0.5, y: from.y + 0.5 }; + const c1 = T({ x: to.x + 0.5, y: to.y + 0.5 }); + + // The line must cross the rim through the open mouth of A. + let t: number, off: number, P: { x: number; y: number }; + if (sideA === "E" || sideA === "W") { + const rimX = sideA === "E" ? mouthA.x + 1 : mouthA.x; + if (Math.abs(c1.x - c0.x) < EPS) continue; + t = (rimX - c0.x) / (c1.x - c0.x); + const y = c0.y + t * (c1.y - c0.y); + off = y - mouthA.y; + P = { x: rimX, y }; + } else { + const rimY = sideA === "S" ? mouthA.y + 1 : mouthA.y; + if (Math.abs(c1.y - c0.y) < EPS) continue; + t = (rimY - c0.y) / (c1.y - c0.y); + const x = c0.x + t * (c1.x - c0.x); + off = x - mouthA.x; + P = { x, y: rimY }; + } + if (t <= EPS || t >= 1 - EPS) continue; + // Grazing the mouth's corners counts as blocked — strict reading. + if (off <= EPS || off >= 1 - EPS) continue; + + const Pfar = Tinv(P); if ( - hasLineOfSight(board, from, mouthA, blockedCells) && - hasLineOfSight(board, mouthB, to, blockedCells) + segmentClear(board, c0.x, c0.y, P.x, P.y, blockedCells, + [cellKey(from), cellKey(mouthA)]) && + segmentClear(board, Pfar.x, Pfar.y, to.x + 0.5, to.y + 0.5, blockedCells, + [cellKey(to), cellKey(mouthB)]) ) { return true; } diff --git a/packages/engine/test/board.test.ts b/packages/engine/test/board.test.ts index f480fb2..2a711b7 100644 --- a/packages/engine/test/board.test.ts +++ b/packages/engine/test/board.test.ts @@ -236,3 +236,30 @@ describe("setup diagram pairings (rulebook Set-Up Diagram)", () => { expect(bent(r.state.board.warps).length).toBe(0); // "only opposite board edges connect" }); }); + +describe("sight through warps treats the boards as adjacent", () => { + const twoSector: SectorPlacement[] = [ + { boardId: "board-a", origin: { x: 0, y: 0 }, rotation: 0 }, + { boardId: "board-b", origin: { x: 0, y: 5 }, rotation: 0 }, + ]; + + it("diagonal lines pass through the one-cell mouth like any doorway", () => { + const board = assembleBoard(twoSector); + // From SOME warp mouth, at least one square off the straight corridor + // axis must be visible through the opening — the adjacency promise. + let offAxis = 0; + for (const w of board.warps) { + const mouth = w.from.cell; + const axisVertical = w.from.side === "N" || w.from.side === "S"; + for (const key of Object.keys(board.cells)) { + const [x, y] = key.split(",").map(Number) as [number, number]; + const onAxis = axisVertical ? x === mouth.x : y === mouth.y; + if (onAxis) continue; + if (!hasLineOfSight(board, mouth, { x, y }) && sightBetween(board, mouth, { x, y })) { + offAxis++; + } + } + } + expect(offAxis).toBeGreaterThan(0); + }); +});