diff --git a/.playwright-mcp/console-2026-08-16T17-53-12-656Z.log b/.playwright-mcp/console-2026-08-16T17-53-12-656Z.log index 5eb1500..d0a8775 100644 --- a/.playwright-mcp/console-2026-08-16T17-53-12-656Z.log +++ b/.playwright-mcp/console-2026-08-16T17-53-12-656Z.log @@ -106,3 +106,241 @@ [ 518839ms] [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-CAp618mF.js:1 [ 522451ms] [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-CAp618mF.js:1 [ 527128ms] [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-CAp618mF.js:1 +[ 531685ms] [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-CAp618mF.js:1 +[ 535957ms] [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-CAp618mF.js:1 +[ 538795ms] [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-CAp618mF.js:1 +[ 543124ms] [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-CAp618mF.js:1 +[ 549494ms] [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-CAp618mF.js:1 +[ 554492ms] [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-CAp618mF.js:1 +[ 558509ms] [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-CAp618mF.js:1 +[ 563139ms] [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-CAp618mF.js:1 +[ 568562ms] [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-CAp618mF.js:1 +[ 573251ms] [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-CAp618mF.js:1 +[ 578506ms] [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-CAp618mF.js:1 +[ 581743ms] [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-CAp618mF.js:1 +[ 586998ms] [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-CAp618mF.js:1 +[ 590328ms] [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-CAp618mF.js:1 +[ 595255ms] [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-CAp618mF.js:1 +[ 599038ms] [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-CAp618mF.js:1 +[ 602039ms] [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-CAp618mF.js:1 +[ 607836ms] [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-CAp618mF.js:1 +[ 610796ms] [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-CAp618mF.js:1 +[ 615157ms] [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-CAp618mF.js:1 +[ 620870ms] [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-CAp618mF.js:1 +[ 627369ms] [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-CAp618mF.js:1 +[ 632975ms] [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-CAp618mF.js:1 +[ 637405ms] [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-CAp618mF.js:1 +[ 638907ms] [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-CAp618mF.js:1 +[ 640412ms] [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-CAp618mF.js:1 +[ 641916ms] [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-CAp618mF.js:1 +[ 643421ms] [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-CAp618mF.js:1 +[ 644930ms] [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-CAp618mF.js:1 +[ 646436ms] [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-CAp618mF.js:1 +[ 647944ms] [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-CAp618mF.js:1 +[ 649452ms] [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-CAp618mF.js:1 +[ 650989ms] [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-CAp618mF.js:1 +[ 652541ms] [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-CAp618mF.js:1 +[ 654188ms] [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-CAp618mF.js:1 +[ 655847ms] [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-CAp618mF.js:1 +[ 657977ms] [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-CAp618mF.js:1 +[ 660684ms] [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-CAp618mF.js:1 +[ 664051ms] [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-CAp618mF.js:1 +[ 668595ms] [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-CAp618mF.js:1 +[ 673293ms] [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-CAp618mF.js:1 +[ 677621ms] [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-CAp618mF.js:1 +[ 684037ms] [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-CAp618mF.js:1 +[ 689808ms] [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-CAp618mF.js:1 +[ 695746ms] [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-CAp618mF.js:1 +[ 698306ms] [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-CAp618mF.js:1 +[ 702945ms] [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-CAp618mF.js:1 +[ 705817ms] [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-CAp618mF.js:1 +[ 710928ms] [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-CAp618mF.js:1 +[ 715275ms] [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-CAp618mF.js:1 +[ 718798ms] [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-CAp618mF.js:1 +[ 721739ms] [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-CAp618mF.js:1 +[ 726637ms] [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-CAp618mF.js:1 +[ 732957ms] [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-CAp618mF.js:1 +[ 738147ms] [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-CAp618mF.js:1 +[ 742270ms] [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-CAp618mF.js:1 +[ 747558ms] [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-CAp618mF.js:1 +[ 751595ms] [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-CAp618mF.js:1 +[ 757878ms] [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-CAp618mF.js:1 +[ 759380ms] [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-CAp618mF.js:1 +[ 760882ms] [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-CAp618mF.js:1 +[ 762384ms] [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-CAp618mF.js:1 +[ 763887ms] [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-CAp618mF.js:1 +[ 765390ms] [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-CAp618mF.js:1 +[ 766898ms] [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-CAp618mF.js:1 +[ 768407ms] [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-CAp618mF.js:1 +[ 769928ms] [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-CAp618mF.js:1 +[ 771456ms] [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-CAp618mF.js:1 +[ 773301ms] [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-CAp618mF.js:1 +[ 774945ms] [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-CAp618mF.js:1 +[ 776750ms] [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-CAp618mF.js:1 +[ 778502ms] [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-CAp618mF.js:1 +[ 780488ms] [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-CAp618mF.js:1 +[ 783155ms] [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-CAp618mF.js:1 +[ 787528ms] [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-CAp618mF.js:1 +[ 791871ms] [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-CAp618mF.js:1 +[ 798122ms] [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-CAp618mF.js:1 +[ 803059ms] [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-CAp618mF.js:1 +[ 807298ms] [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-CAp618mF.js:1 +[ 813473ms] [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-CAp618mF.js:1 +[ 816831ms] [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-CAp618mF.js:1 +[ 819506ms] [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-CAp618mF.js:1 +[ 824823ms] [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-CAp618mF.js:1 +[ 827747ms] [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-CAp618mF.js:1 +[ 831197ms] [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-CAp618mF.js:1 +[ 837479ms] [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-CAp618mF.js:1 +[ 842361ms] [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-CAp618mF.js:1 +[ 847902ms] [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-CAp618mF.js:1 +[ 851866ms] [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-CAp618mF.js:1 +[ 858060ms] [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-CAp618mF.js:1 +[ 860876ms] [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-CAp618mF.js:1 +[ 866298ms] [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-CAp618mF.js:1 +[ 872252ms] [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-CAp618mF.js:1 +[ 877633ms] [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-CAp618mF.js:1 +[ 883956ms] [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-CAp618mF.js:1 +[ 888030ms] [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-CAp618mF.js:1 +[ 891523ms] [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-CAp618mF.js:1 +[ 894404ms] [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-CAp618mF.js:1 +[ 897780ms] [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-CAp618mF.js:1 +[ 902616ms] [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-CAp618mF.js:1 +[ 907855ms] [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-CAp618mF.js:1 +[ 914182ms] [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-CAp618mF.js:1 +[ 919017ms] [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-CAp618mF.js:1 +[ 924366ms] [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-CAp618mF.js:1 +[ 930117ms] [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-CAp618mF.js:1 +[ 933514ms] [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-CAp618mF.js:1 +[ 937308ms] [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-CAp618mF.js:1 +[ 941885ms] [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-CAp618mF.js:1 +[ 944709ms] [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-CAp618mF.js:1 +[ 947727ms] [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-CAp618mF.js:1 +[ 954108ms] [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-CAp618mF.js:1 +[ 958510ms] [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-CAp618mF.js:1 +[ 961051ms] [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-CAp618mF.js:1 +[ 965152ms] [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-CAp618mF.js:1 +[ 970130ms] [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-CAp618mF.js:1 +[ 973824ms] [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-CAp618mF.js:1 +[ 978781ms] [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-CAp618mF.js:1 +[ 981419ms] [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-CAp618mF.js:1 +[ 985895ms] [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-CAp618mF.js:1 +[ 991092ms] [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-CAp618mF.js:1 +[ 995656ms] [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-CAp618mF.js:1 +[ 999330ms] [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-CAp618mF.js:1 +[ 1000833ms] [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-CAp618mF.js:1 +[ 1002334ms] [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-CAp618mF.js:1 +[ 1003836ms] [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-CAp618mF.js:1 +[ 1005338ms] [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-CAp618mF.js:1 +[ 1006841ms] [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-CAp618mF.js:1 +[ 1008347ms] [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-CAp618mF.js:1 +[ 1009853ms] [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-CAp618mF.js:1 +[ 1011363ms] [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-CAp618mF.js:1 +[ 1012892ms] [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-CAp618mF.js:1 +[ 1014431ms] [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-CAp618mF.js:1 +[ 1016008ms] [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-CAp618mF.js:1 +[ 1017804ms] [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-CAp618mF.js:1 +[ 1019636ms] [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-CAp618mF.js:1 +[ 1021968ms] [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-CAp618mF.js:1 +[ 1025918ms] [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-CAp618mF.js:1 +[ 1030911ms] [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-CAp618mF.js:1 +[ 1036872ms] [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-CAp618mF.js:1 +[ 1042476ms] [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-CAp618mF.js:1 +[ 1046566ms] [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-CAp618mF.js:1 +[ 1049648ms] [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-CAp618mF.js:1 +[ 1054344ms] [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-CAp618mF.js:1 +[ 1058372ms] [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-CAp618mF.js:1 +[ 1063206ms] [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-CAp618mF.js:1 +[ 1067191ms] [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-CAp618mF.js:1 +[ 1070939ms] [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-CAp618mF.js:1 +[ 1075397ms] [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-CAp618mF.js:1 +[ 1080940ms] [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-CAp618mF.js:1 +[ 1085883ms] [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-CAp618mF.js:1 +[ 1090965ms] [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-CAp618mF.js:1 +[ 1095519ms] [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-CAp618mF.js:1 +[ 1101984ms] [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-CAp618mF.js:1 +[ 1108035ms] [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-CAp618mF.js:1 +[ 1110627ms] [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-CAp618mF.js:1 +[ 1115173ms] [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-CAp618mF.js:1 +[ 1117746ms] [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-CAp618mF.js:1 +[ 1123195ms] [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-CAp618mF.js:1 +[ 1126552ms] [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-CAp618mF.js:1 +[ 1129802ms] [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-CAp618mF.js:1 +[ 1135218ms] [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-CAp618mF.js:1 +[ 1140687ms] [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-CAp618mF.js:1 +[ 1146402ms] [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-CAp618mF.js:1 +[ 1150144ms] [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-CAp618mF.js:1 +[ 1156238ms] [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-CAp618mF.js:1 +[ 1161595ms] [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-CAp618mF.js:1 +[ 1168027ms] [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-CAp618mF.js:1 +[ 1173338ms] [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-CAp618mF.js:1 +[ 1177064ms] [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-CAp618mF.js:1 +[ 1183027ms] [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-CAp618mF.js:1 +[ 1188131ms] [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-CAp618mF.js:1 +[ 1191735ms] [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-CAp618mF.js:1 +[ 1195157ms] [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-CAp618mF.js:1 +[ 1197737ms] [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-CAp618mF.js:1 +[ 1203300ms] [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-CAp618mF.js:1 +[ 1209522ms] [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-CAp618mF.js:1 +[ 1213650ms] [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-CAp618mF.js:1 +[ 1219100ms] [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-CAp618mF.js:1 +[ 1223494ms] [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-CAp618mF.js:1 +[ 1229841ms] [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-CAp618mF.js:1 +[ 1235732ms] [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-CAp618mF.js:1 +[ 1240814ms] [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-CAp618mF.js:1 +[ 1242316ms] [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-CAp618mF.js:1 +[ 1243818ms] [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-CAp618mF.js:1 +[ 1245320ms] [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-CAp618mF.js:1 +[ 1246822ms] [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-CAp618mF.js:1 +[ 1248325ms] [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-CAp618mF.js:1 +[ 1249830ms] [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-CAp618mF.js:1 +[ 1251342ms] [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-CAp618mF.js:1 +[ 1252857ms] [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-CAp618mF.js:1 +[ 1254399ms] [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-CAp618mF.js:1 +[ 1255953ms] [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-CAp618mF.js:1 +[ 1257511ms] [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-CAp618mF.js:1 +[ 1259263ms] [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-CAp618mF.js:1 +[ 1261221ms] [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-CAp618mF.js:1 +[ 1263410ms] [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-CAp618mF.js:1 +[ 1267294ms] [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-CAp618mF.js:1 +[ 1271995ms] [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-CAp618mF.js:1 +[ 1275306ms] [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-CAp618mF.js:1 +[ 1279944ms] [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-CAp618mF.js:1 +[ 1282751ms] [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-CAp618mF.js:1 +[ 1286088ms] [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-CAp618mF.js:1 +[ 1291529ms] [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-CAp618mF.js:1 +[ 1296577ms] [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-CAp618mF.js:1 +[ 1301422ms] [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-CAp618mF.js:1 +[ 1304057ms] [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-CAp618mF.js:1 +[ 1307586ms] [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-CAp618mF.js:1 +[ 1312541ms] [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-CAp618mF.js:1 +[ 1316164ms] [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-CAp618mF.js:1 +[ 1322638ms] [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-CAp618mF.js:1 +[ 1328537ms] [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-CAp618mF.js:1 +[ 1333627ms] [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-CAp618mF.js:1 +[ 1337920ms] [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-CAp618mF.js:1 +[ 1341347ms] [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-CAp618mF.js:1 +[ 1347133ms] [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-CAp618mF.js:1 +[ 1350410ms] [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-CAp618mF.js:1 +[ 1356545ms] [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-CAp618mF.js:1 +[ 1359840ms] [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-CAp618mF.js:1 +[ 1364706ms] [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-CAp618mF.js:1 +[ 1370998ms] [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-CAp618mF.js:1 +[ 1376683ms] [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-CAp618mF.js:1 +[ 1380372ms] [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-CAp618mF.js:1 +[ 1385933ms] [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-CAp618mF.js:1 +[ 1390373ms] [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-CAp618mF.js:1 +[ 1395807ms] [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-CAp618mF.js:1 +[ 1402308ms] [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-CAp618mF.js:1 +[ 1406555ms] [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-CAp618mF.js:1 +[ 1411992ms] [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-CAp618mF.js:1 +[ 1418370ms] [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-CAp618mF.js:1 +[ 1423620ms] [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-CAp618mF.js:1 +[ 1426621ms] [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-CAp618mF.js:1 +[ 1431576ms] [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-CAp618mF.js:1 +[ 1437386ms] [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-CAp618mF.js:1 +[ 1441649ms] [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-CAp618mF.js:1 +[ 1447868ms] [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-CAp618mF.js:1 +[ 1452368ms] [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-CAp618mF.js:1 +[ 1458783ms] [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-CAp618mF.js:1 +[ 1462262ms] [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-CAp618mF.js:1 diff --git a/packages/engine/src/board.ts b/packages/engine/src/board.ts index 48ebc44..16547a9 100644 --- a/packages/engine/src/board.ts +++ b/packages/engine/src/board.ts @@ -190,7 +190,7 @@ export function assembleBoard( // Wraparound warps. An opening that faces another sector is a corridor, not // a warp. The remaining outer-boundary openings pair per options.warpPairs // (from the setup diagram's letters) or, by default, straight across the - // map. (L-shaped 3p aisle-warp corners are TODO.) + // map — including the aisle-warp corner arcs, which are just warp pairs. const warps: Warp[] = []; const inMap = (c: Cell) => cells[cellKey(c)] === true; const openingCell = (sectorIndex: number, side: Side): Cell => { diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index b289eaa..df5decb 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -178,7 +178,7 @@ export interface CastStack { powerAttackPoints: number; params: CastParams | null; kind: "spell" | "physical"; - counters: { player: PlayerId; card: CardInstance; nullified: boolean }[]; + counters: { player: PlayerId; card: CardInstance; nullified: boolean; cell?: Cell }[]; waitingOn: PlayerId; /** CHAOS only: the defender's FULL SHIELD sat them out rather than stopping it. */ defenderShielded?: boolean; @@ -186,6 +186,8 @@ export interface CastStack { creatureId?: string; /** The wraith's touch also steals a random card if damage lands. */ creatureTouch?: "wraith" | "claw"; + /** A spell freed from slime: counteractions cannot touch its caster. */ + trapped?: boolean; } export interface CastParams { @@ -212,7 +214,9 @@ export interface GameConfig { * Absent = original. Rev 2: LIFESAVER leaves two-player decks ("Not * applicable in a 2-player game."). Rev 3: WARD springs only when armed, * and CHAOS honors FULL SHIELD sit-outs and refuses REFLECTIONS. Rev 4: - * creature blows open a counteraction window like any attack. + * creature blows open a counteraction window like any attack. Rev 5: + * BIG MAN pushes occupants ahead, steps over floor hazards for 2 points, + * and bars monsters from his square. */ deckRev?: number; } @@ -225,6 +229,8 @@ export interface GameState { edgeOverrides: Record; /** Accumulated attack damage per edge: a wall falls at 20, a door at 15. */ wallDamage: Record; + /** Spells stuck in slime, waiting for the next visitor (by cell key). */ + slimeTraps: Record; /** Players whose WARD is set to spring (rules rev 3+; their secret). */ wardArmed: PlayerId[]; /** CHAOS is landing: each queued player may play FULL SHIELD to sit out. */ @@ -536,6 +542,10 @@ export type GameEvent = | { type: "wardSprung"; owner: PlayerId; victim: PlayerId } | { type: "wardSet"; player: PlayerId; armed: boolean; visibleTo: PlayerId } | { type: "chaosShielded"; player: PlayerId } + | { type: "spellTrapped"; caster: PlayerId; cell: Cell; cardId: string } + | { type: "slimeTrapSprung"; cell: Cell; cardId: string; victim: PlayerId } + | { type: "slimeWashed"; cell: Cell } + | { type: "pushed"; by: PlayerId; player?: PlayerId; creatureId?: string; from: Cell; to: Cell } | { type: "curseRemoved"; caster: PlayerId; target: PlayerId; cardId: string } | { type: "objectEnchanted"; caster: PlayerId; cardId: string } | { type: "warpTokensPlaced"; caster: PlayerId; a: Cell; b: Cell } @@ -585,7 +595,7 @@ export type CastTarget = | { kind: "cell"; cell: Cell }; export type Command = - | { type: "move"; direction: Side } + | { type: "move"; direction: Side; over?: boolean } | { type: "playNumberForMovement"; instanceId: string; addInstanceId?: string } | { type: "punch"; targetId: PlayerId } | { type: "punchWall"; cell: Cell; side: Side } @@ -616,7 +626,7 @@ export type Command = } | { type: "setAmbush"; instanceId: string; trigger: AmbushTrigger; spellInstanceId: string; numberInstanceIds?: string[] } | { type: "cancelAmbush"; ambushId: string } - | { type: "counteract"; instanceId: string } + | { type: "counteract"; instanceId: string; params?: { cell?: Cell } } | { type: "pass" } | { type: "pickUpTreasure" } | { type: "pickUpObject"; instanceId: string } @@ -1181,8 +1191,8 @@ const CARD_EFFECTS: Record }, "wall-of-fire": { kind: "neutral", - // Neutral use: a burning barrier for [duration] turns. (Counteraction - // use vs WATERBOLT: TODO.) + // Neutral use: a burning barrier for [duration] turns. (Its counteraction + // use against WATERBOLT lives in doCounteract and the resolution pipe.) resolve: (state, events, caster, cmd, magnitude) => { if (!cmd.target || cmd.target.kind !== "edge") return "wall of fire targets a corridor edge"; const { cell, side } = cmd.target; @@ -2293,6 +2303,11 @@ function waveFromEdge(state: GameState, events: GameEvent[], cell: Cell, side: S destroyCreature(state, events, c, reason); } } + if (state.squareContents[cellKey(probe)]?.kind === "slime") { + delete state.squareContents[cellKey(probe)]; + delete state.slimeTraps[cellKey(probe)]; + events.push({ type: "slimeWashed", cell: probe }); + } probe = neighbor(probe, dir); } } @@ -2526,6 +2541,11 @@ function doMoveCreature(prev: GameState, creatureId: string, direction: Side): C } else { const content = state.squareContents[cellKey(target.to)]; if (content?.kind === "stone") return err("that square is solid stone"); + if ((state.config.deckRev ?? 1) >= 5 && + state.players.some((o) => o.alive && cellKey(o.position) === cellKey(target.to) && + sustainedOn(state, o.id, "big-man").length > 0)) { + return err("a giant fills that corridor"); + } creature.position = target.to; } creature.movementUsed++; @@ -2569,6 +2589,32 @@ function doMoveCreature(prev: GameState, creatureId: string, direction: Side): C return { ok: true, state, events }; } +/** A spell stuck in slime "goes off only once" at whoever is in the gel. */ +function springSlimeTrap(state: GameState, events: GameEvent[], victim: PlayerState): void { + if (state.stack) return; // one thing at a time; the next visitor springs it + const key = cellKey(victim.position); + const traps = state.slimeTraps[key]; + if (!traps || traps.length === 0) return; + const trap = traps.shift()!; + if (traps.length === 0) delete state.slimeTraps[key]; + state.discard.push(trap.card); + events.push({ type: "slimeTrapSprung", cell: victim.position, cardId: trap.card.cardId, victim: victim.id }); + state.stack = { + attackerId: trap.casterId, + defenderId: victim.id, + attackCard: trap.card, + numberValue: trap.numberValue, + amplifyFactor: trap.amplifyFactor, + extendFactor: 1, + powerAttackPoints: 0, + params: null, + kind: (CARD_EFFECTS[trap.card.cardId] as AttackEffect | undefined)?.physical ? "physical" : "spell", + counters: [], + waitingOn: victim.id, + trapped: true, + }; +} + /** Rules rev 4: a creature's blow opens a counteraction window like any attack. */ function openCreatureStack( state: GameState, @@ -2719,6 +2765,7 @@ function remapState( state.illusionWalls = remapRecord(state.illusionWalls, mapEdgeKey); state.openDoorEdges = state.openDoorEdges.map(mapEdgeKey); state.squareContents = remapRecord(state.squareContents, mapCellKey); + state.slimeTraps = remapRecord(state.slimeTraps, mapCellKey); state.groundObjects = remapRecord(state.groundObjects, mapCellKey); for (const fx of state.sustained) { if (fx.edge) fx.edge = mapEdgeKey(fx.edge); @@ -3008,6 +3055,7 @@ export function createGame(config: GameConfig): { state: GameState; events: Game board, edgeOverrides: {}, wallDamage: {}, + slimeTraps: {}, wardArmed: [], chaosPending: null, doorStates: {}, @@ -3082,7 +3130,7 @@ export function applyCommand(state: GameState, playerId: PlayerId, command: Comm if (state.stack) { if (playerId !== state.stack.waitingOn) return err("waiting for another player's response"); - if (command.type === "counteract") return doCounteract(state, playerId, command.instanceId); + if (command.type === "counteract") return doCounteract(state, playerId, command.instanceId, command.params); if (command.type === "pass") return doPass(state, playerId); return err("an attack is being resolved — counteract or pass"); } @@ -3186,7 +3234,7 @@ export function applyCommand(state: GameState, playerId: PlayerId, command: Comm } switch (command.type) { - case "move": return doMove(state, command.direction); + case "move": return doMove(state, command.direction, command.over === true); case "playNumberForMovement": return doPlayNumberForMovement(state, command.instanceId, command.addInstanceId); case "punch": return doPunch(state, command.targetId); case "punchWall": return doPunchWall(state, command.cell, command.side); @@ -3242,7 +3290,7 @@ function blindBump(state: GameState, events: GameEvent[], p: PlayerState, direct return { ok: true, state, events }; } -function doMove(prev: GameState, direction: Side): CommandResult { +function doMove(prev: GameState, direction: Side, over = false): CommandResult { const blocked = requireActionsAvailable(prev); if (blocked) return err(blocked); if (prev.turn.movementUsed >= prev.turn.movementAllowance) return err("no movement left"); @@ -3279,6 +3327,24 @@ function doMove(prev: GameState, direction: Side): CommandResult { } const view = boardView(state); + // BIG MAN: "Using 2 movement points, you can step over a PIT, TACKS, or + // KILLER OOZE" (rules rev 5). The giant strides the hazard square without + // entering it — one point charged here, then the normal step below lands + // him beyond it with every usual arrival effect (and the second point). + if (over) { + const big = (state.config.deckRev ?? 1) >= 5 && sustainedOn(state, p.id, "big-man").length > 0; + if (!big) return err("only a giant steps over hazards"); + if (isBlinded(state, p)) return err("you cannot leap what you cannot see"); + if (state.turn.movementAllowance - state.turn.movementUsed < 2) return err("stepping over costs 2 movement"); + const hop1 = stepTarget(view, p.position, direction); + if (hop1.kind !== "step") return err("nothing to step over that way"); + const hazard = state.squareContents[cellKey(hop1.to)]?.kind; + if (hazard !== "pit" && hazard !== "tacks" && hazard !== "ooze") { + return err("you may step over a pit, tacks, or killer ooze"); + } + state.turn.movementUsed++; + p.position = hop1.to; + } const target = stepTarget(view, p.position, direction); const misted = isMisted(state, p.id); @@ -3332,6 +3398,41 @@ function doMove(prev: GameState, direction: Side): CommandResult { return err("a giant fills that corridor"); } } + // BIG MAN pushes: "You can push monsters or other players (in an adjacent + // square) down the corridor ahead of you as you move" (rules rev 5). No + // room to shove them onward means no way forward for the giant either. + if ((state.config.deckRev ?? 1) >= 5 && via === "step" && + sustainedOn(state, p.id, "big-man").length > 0) { + const here = cellKey(p.position); + const pushPlayers = state.players.filter((o) => o.id !== p.id && o.alive && cellKey(o.position) === here); + const pushCreatures = state.creatures.filter((c) => cellKey(c.position) === here); + if (pushPlayers.length > 0 || pushCreatures.length > 0) { + if (pushPlayers.some((o) => isLockedInPlace(state, o.id))) { + p.position = from; + return err("someone there is stuck fast and cannot be pushed"); + } + const shove = stepTarget(view, p.position, direction); + const shoveOk = + shove.kind === "step" && + state.squareContents[cellKey(shove.to)]?.kind !== "stone" && + !state.players.some((o) => o.alive && cellKey(o.position) === cellKey(shove.to) && + sustainedOn(state, o.id, "big-man").length > 0); + if (!shoveOk) { + p.position = from; + return err("no room to push them onward"); + } + for (const o of pushPlayers) { + const oFrom = o.position; + o.position = { ...shove.to }; + events.push({ type: "pushed", by: p.id, player: o.id, from: oFrom, to: o.position }); + } + for (const c of pushCreatures) { + const cFrom = c.position; + c.position = { ...shove.to }; + events.push({ type: "pushed", by: p.id, creatureId: c.id, from: cFrom, to: c.position }); + } + } + } // FEAR: no one moves within 3 spaces of the fearsome one. for (const other of state.players) { if (other.id === p.id || !other.alive) continue; @@ -3452,10 +3553,12 @@ function doMove(prev: GameState, direction: Side): CommandResult { } checkVictory(state, events); } - // FILL SQUARE WITH SLIME: entering ends your turn's actions. + // FILL SQUARE WITH SLIME: entering ends your turn's actions — and any + // spell stuck in the gel goes off at the visitor. if (content?.kind === "slime" && p.alive && !misted) { events.push({ type: "stuckInSlime", player: p.id, at: p.position }); state.turn.actionsEnded = true; + springSlimeTrap(state, events, p); } // IDIOT lifts when the victim reaches their own treasure. if (sustainedOn(state, p.id, "idiot").length > 0) { @@ -4005,6 +4108,47 @@ function doCast(prev: GameState, cmd: Extract): Comma } return { ok: true, state, events }; } + // FILL SQUARE WITH SLIME: "Spells cast at the slime get stuck there, and + // affect anyone in the slime or entering it later on." A 5-point WATERBOLT + // washes the slime away instead. + if (cmd.target?.kind === "cell" && + state.squareContents[cellKey(cmd.target.cell)]?.kind === "slime") { + const cell = cmd.target.cell; + if (effect.requiresLos && !casterLos(state, caster, caster.position, cell)) { + return err("no line of sight to the slime"); + } + const wandEvents: GameEvent[] = []; + { + const werr = spendWandCharge(state, caster, wandEvents); + if (werr) return err(werr); + } + consumeCast(state, caster, inHand, mods, effect.keepInHand ?? false); + if (state.turn.attackUsed) state.turn.secondAttackUsed = true; + state.turn.attackUsed = true; + state.lastSpellUsed[caster.id] = inHand.cardId; + const events2: GameEvent[] = [...wandEvents]; + if (inHand.cardId === "waterbolt" && mods.magnitude.power >= 5) { + delete state.squareContents[cellKey(cell)]; + delete state.slimeTraps[cellKey(cell)]; + events2.push({ type: "slimeWashed", cell }); + return { ok: true, state, events: events2 }; + } + // The card leaves the discard and lives in the gel. + const di = state.discard.findIndex((c) => c.instanceId === inHand.instanceId); + if (di !== -1) state.discard.splice(di, 1); + const key = cellKey(cell); + state.slimeTraps[key] = [...(state.slimeTraps[key] ?? []), { + card: inHand, casterId: caster.id, + numberValue: mods.magnitude.numberValue, + amplifyFactor: 2 ** mods.amplifies.length, + }]; + events2.push({ type: "spellTrapped", caster: caster.id, cell, cardId: inHand.cardId }); + // "affect anyone in the slime": a current occupant springs it at once. + const occupant = state.players.find((p) => p.alive && p.id !== caster.id && cellKey(p.position) === key); + if (occupant) springSlimeTrap(state, events2, occupant); + return { ok: true, state, events: events2 }; + } + // "Any attack against an inanimate object counts as your one attack for // the turn." A wall or door soaks the spell's damage; no counteractions. if (cmd.target?.kind === "edge") { @@ -4357,7 +4501,7 @@ function checkAmbushes( } } -function doCounteract(prev: GameState, playerId: PlayerId, instanceId: string): CommandResult { +function doCounteract(prev: GameState, playerId: PlayerId, instanceId: string, params?: { cell?: Cell }): CommandResult { const state = clone(prev); const stack = state.stack!; const player = state.players.find((p) => p.id === playerId)!; @@ -4404,6 +4548,31 @@ function doCounteract(prev: GameState, playerId: PlayerId, instanceId: string): return { ok: true, state, events }; } + // TELEPORT: "If you use this spell as a Counteraction, the attack has no + // chance of hitting you" (official FAQ). The escape happens at resolution, + // so an ANTI-ANTI can still pin your boots to the floor. + if (card.cardId === "teleport") { + const to = params?.cell; + if (!to) return err("teleport needs a destination cell"); + if (isLockedInPlace(state, playerId)) return err("you are locked in place"); + const view = boardView(state); + if (!view.cells[cellKey(to)]) return err("destination is off the board"); + if (state.squareContents[cellKey(to)]?.kind === "stone") return err("that square is solid stone"); + if (wallIgnoringDistance(view, player.position, to) > 4) { + return err("teleport reaches at most four spaces"); + } + takeFromHand(player, instanceId); + state.discard.push(card); + stack.counters.push({ player: playerId, card, nullified: false, cell: to }); + stack.waitingOn = stack.attackerId; + state.lastSpellUsed[playerId] = card.cardId; + return { + ok: true, + state, + events: [{ type: "counteractionPlayed", player: playerId, card, cardId: card.cardId, against: stack.attackCard?.cardId ?? "punch" }], + }; + } + // WALL OF FIRE: "As a COUNTERACTION, it will stop a WATERBOLT." if (card.cardId === "wall-of-fire") { if (stack.attackCard?.cardId !== "waterbolt") { @@ -4582,6 +4751,12 @@ function resolveStack(state: GameState, events: GameEvent[]): void { pipe.fullyStopped = true; continue; } + if (counter.card.cardId === "teleport") { + pipe.damage = 0; + pipe.duration = 0; + pipe.fullyStopped = true; + continue; + } if (stack.creatureId && (counter.card.cardId === "reflection" || counter.card.cardId === "full-reflection")) { // "REFLECTIONs used on the wraith's touch will damage the wraith." @@ -4598,13 +4773,23 @@ function resolveStack(state: GameState, events: GameEvent[]): void { if (ce && ce.kind === "counter") ce.apply(pipe); } + // A surviving teleport counter whisks the defender away before anything lands. + const escape = stack.counters.find((c) => !c.nullified && c.card.cardId === "teleport" && c.cell); + if (escape) { + const from = defender.position; + defender.position = { ...escape.cell! }; + events.push({ type: "teleported", player: defender.id, from, to: defender.position, by: defender.id, cardId: "teleport" }); + } + let damageDealt = 0; const attackingCreature = stack.creatureId ? state.creatures.find((c) => c.id === stack.creatureId) : undefined; if (pipe.redirected) { if (pipe.damage > 0) { - if (attackingCreature) { + if (stack.trapped) { + // "Counteractions against 'trapped' attacks do not affect the caster." + } else if (attackingCreature) { damageCreature(state, events, attackingCreature, pipe.damage, "reflected touch"); } else { applyDamage(state, events, attacker, pipe.damage, `${attackId} (reflected)`, defender.id); @@ -4634,7 +4819,7 @@ function resolveStack(state: GameState, events: GameEvent[]): void { state.discard.push(card!); events.push({ type: "cardsDiscarded", player: defender.id, cards: [card!] }); } - if (pipe.reflectedDamage > 0) { + if (pipe.reflectedDamage > 0 && !stack.trapped) { if (attackingCreature) { damageCreature(state, events, attackingCreature, pipe.reflectedDamage, "reflected touch"); } else { diff --git a/packages/engine/src/setups.ts b/packages/engine/src/setups.ts index 58aa713..f8a3b4d 100644 --- a/packages/engine/src/setups.ts +++ b/packages/engine/src/setups.ts @@ -2,9 +2,10 @@ // shuffled and randomly rotated, then laid out per player count; each player // gets the sector containing their home star. // -// Implemented: 2 players (1x2 column, crossed side pairings per the diagram) -// and 4 players (2x2 square, straight-across pairings). 3 players (L-shape -// with the AUTO WARP corner connector) and 5+ (two sets) are TODO. +// All five layouts are verified against the rulebook's Set-Up Diagram +// (research/images/6e-rules-setup-diagram.jpg): 2p crossed pairs, 3p stair +// with the Aisle Warp arc, 4p and 6p straight-across, 5p plus with corner +// warps at every notch. board.test.ts pins each pairing. import { assembleBoard, diff --git a/packages/engine/test/board.test.ts b/packages/engine/test/board.test.ts index 8b23f48..f480fb2 100644 --- a/packages/engine/test/board.test.ts +++ b/packages/engine/test/board.test.ts @@ -173,3 +173,66 @@ describe("line of sight through wraparound openings", () => { expect(sightBetween(board, { x: 2, y: 0 }, { x: 2, y: 8 }, stone)).toBe(false); }); }); + +import { createRng } from "../src/rng"; +import { setupBoard as setupBoardForPins } from "../src/setups"; + +describe("setup diagram pairings (rulebook Set-Up Diagram)", () => { + const setupBoard = setupBoardForPins; + + function onEdge(c: { x: number; y: number }, origin: { x: number; y: number }, side: string): boolean { + if (side === "N") return c.y === origin.y && c.x >= origin.x && c.x < origin.x + 5; + if (side === "S") return c.y === origin.y + 4 && c.x >= origin.x && c.x < origin.x + 5; + if (side === "W") return c.x === origin.x && c.y >= origin.y && c.y < origin.y + 5; + return c.x === origin.x + 4 && c.y >= origin.y && c.y < origin.y + 5; + } + function hasWarp(board: { warps: { from: { cell: { x: number; y: number } }; to: { cell: { x: number; y: number } } }[] }, + a: { o: { x: number; y: number }; s: string }, b: { o: { x: number; y: number }; s: string }): boolean { + return board.warps.some((w) => onEdge(w.from.cell, a.o, a.s) && onEdge(w.to.cell, b.o, b.s)); + } + + it("3 players: the aisle-warp arc plus B, C, A letter pairs", () => { + const { board } = setupBoard(3, createRng(7)); + const top = { x: 5, y: 0 }, bl = { x: 0, y: 5 }, br = { x: 5, y: 5 }; + expect(hasWarp(board, { o: top, s: "W" }, { o: bl, s: "N" })).toBe(true); // Aisle Warp + expect(hasWarp(board, { o: top, s: "N" }, { o: br, s: "S" })).toBe(true); // B + expect(hasWarp(board, { o: top, s: "E" }, { o: bl, s: "S" })).toBe(true); // C + expect(hasWarp(board, { o: bl, s: "W" }, { o: br, s: "E" })).toBe(true); // A + }); + + it("5 players: opposite tips wrap, notch corners arc around", () => { + const { board } = setupBoard(5, createRng(7)); + const n = { x: 5, y: 0 }, w = { x: 0, y: 5 }, e = { x: 10, y: 5 }, s = { x: 5, y: 10 }; + expect(hasWarp(board, { o: n, s: "N" }, { o: s, s: "S" })).toBe(true); // A + expect(hasWarp(board, { o: w, s: "W" }, { o: e, s: "E" })).toBe(true); // B + expect(hasWarp(board, { o: n, s: "W" }, { o: w, s: "N" })).toBe(true); // corners + expect(hasWarp(board, { o: n, s: "E" }, { o: e, s: "N" })).toBe(true); + expect(hasWarp(board, { o: s, s: "W" }, { o: w, s: "S" })).toBe(true); + expect(hasWarp(board, { o: s, s: "E" }, { o: e, s: "S" })).toBe(true); + }); + + it("6 players: straight-across wrap on both axes", () => { + const { board } = setupBoard(6, createRng(7)); + for (const w of board.warps) { + const colinear = w.from.cell.x === w.to.cell.x || w.from.cell.y === w.to.cell.y; + expect(colinear).toBe(true); + } + }); + + it("relocation discards the aisle warp: only opposite edges connect after", async () => { + const { createGame, applyCommand, activePlayer } = await import("../src/game"); + let { state } = createGame({ playerIds: ["a", "b", "c"], seed: 7, sets: ["basic"], deckRev: 5 }); + const bent = (warps: typeof state.board.warps) => + warps.filter((w) => w.from.cell.x !== w.to.cell.x && w.from.cell.y !== w.to.cell.y); + expect(bent(state.board.warps).length).toBeGreaterThan(0); // the arc exists + const me = activePlayer(state); + me.hand[0] = { instanceId: "rel#1", cardId: "relocate-sector" }; + // Slide the top sector from (5,0) across to (0,0): an L becomes a block. + const r = applyCommand(state, me.id, { + type: "cast", instanceId: "rel#1", + target: { kind: "cell", cell: { x: 0, y: 0 } }, params: { cell: { x: 7, y: 2 } }, + }); + if (!r.ok) throw new Error(r.error); + expect(bent(r.state.board.warps).length).toBe(0); // "only opposite board edges connect" + }); +}); diff --git a/packages/engine/test/casting.test.ts b/packages/engine/test/casting.test.ts index 5c4d92e..9650182 100644 --- a/packages/engine/test/casting.test.ts +++ b/packages/engine/test/casting.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { applyCommand, activePlayer, boardView, createGame } from "../src/game"; -import { cellKey, edgeKey, hasLineOfSight, neighbor, type Side, SIDES } from "../src/board"; +import { cellKey, edgeKey, hasLineOfSight, neighbor, type Side, SIDES, stepTarget } from "../src/board"; import type { CardInstance } from "../src/cards"; import { newGame, must, giveCard, toRound2, faceOff } from "./helpers"; @@ -534,3 +534,139 @@ describe("zero-damage utility attacks", () => { expect(result.state.players.find((p) => p.id === defender)!.carriedTreasureId).toBe(treasure.id); }); }); + +describe("teleport as a counteraction", () => { + it("the attack has no chance of hitting you — you are simply elsewhere", () => { + let { state } = newGame(); + state = toRound2(state); + const { attacker, defender } = faceOff(state); + const fb = giveCard(state, attacker, "fireball"); + const tp = giveCard(state, defender, "teleport", "TP", 0); + const d = state.players.find((p) => p.id === defender)!; + const escape = { x: d.position.x, y: d.position.y + 1 }; + state = must(state, attacker, { + type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: defender }, + }); + state = must(state, defender, { type: "counteract", instanceId: tp.instanceId, params: { cell: escape } }); + state = must(state, attacker, { type: "pass" }); + state = must(state, defender, { type: "pass" }); + const after = state.players.find((p) => p.id === defender)!; + expect(after.life).toBe(15); + expect(cellKey(after.position)).toBe(cellKey(escape)); + }); + + it("ANTI-ANTI pins the boots: the escape never happens and the spell lands", () => { + let { state } = newGame(); + state = toRound2(state); + const { attacker, defender } = faceOff(state); + const fb = giveCard(state, attacker, "fireball"); + const aa = giveCard(state, attacker, "anti-anti", "AA", 1); + const tp = giveCard(state, defender, "teleport", "TP", 0); + const d = state.players.find((p) => p.id === defender)!; + const home = { ...d.position }; + state = must(state, attacker, { + type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: defender }, + }); + state = must(state, defender, { type: "counteract", instanceId: tp.instanceId, params: { cell: { x: home.x, y: home.y + 1 } } }); + state = must(state, attacker, { type: "counteract", instanceId: aa.instanceId }); + state = must(state, defender, { type: "pass" }); + const after = state.players.find((p) => p.id === defender)!; + expect(cellKey(after.position)).toBe(cellKey(home)); + expect(after.life).toBe(10); + }); + + it("reaches at most four spaces", () => { + let { state } = newGame(); + state = toRound2(state); + const { attacker, defender } = faceOff(state); + const fb = giveCard(state, attacker, "fireball"); + const tp = giveCard(state, defender, "teleport", "TP", 0); + const d = state.players.find((p) => p.id === defender)!; + state = must(state, attacker, { + type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: defender }, + }); + const far = { x: d.position.x, y: d.position.y + 5 }; + expect(applyCommand(state, defender, { + type: "counteract", instanceId: tp.instanceId, params: { cell: far }, + }).ok).toBe(false); + }); +}); + +describe("slime holds spells", () => { + function slimeRig() { + let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 5 }); + state = toRound2(state); + const caster = activePlayer(state); + const victim = state.players.find((p) => p.id !== caster.id)!; + // A slime square beside the victim, with the victim adjacent to walk in. + const spot = { x: victim.position.x, y: victim.position.y }; + const cellBeside = (() => { + const view = boardView(state); + for (const side of SIDES) { + const t = stepTarget(view, spot, side); + if (t.kind === "step" && !state.players.some((p) => cellKey(p.position) === cellKey(t.to)) && + !boardView(state).homes.some((h) => cellKey(h) === cellKey(t.to))) { + return { cell: t.to, side }; + } + } + throw new Error("setup: no free square beside the victim"); + })(); + state.squareContents[cellKey(cellBeside.cell)] = { kind: "slime", damage: 0, createdBy: caster.id }; + // The caster stands with the victim: one open edge to the slime = clear LOS. + caster.position = { ...victim.position }; + return { state, caster: caster.id, victim: victim.id, slimeAt: cellBeside }; + } + + it("a fireball cast at the slime waits, then goes off on the next visitor", () => { + const rig = slimeRig(); + let state = rig.state; + const fb = giveCard(state, rig.caster, "fireball"); + state = must(state, rig.caster, { + type: "cast", instanceId: fb.instanceId, target: { kind: "cell", cell: rig.slimeAt.cell }, + }); + expect(Object.keys(state.slimeTraps).length).toBe(1); + state = must(state, rig.caster, { type: "endTurn", draw: 0 }); + // The victim walks in: stuck, and the fireball erupts. + state = must(state, rig.victim, { type: "move", direction: rig.slimeAt.side }); + expect(state.stack?.attackCard?.cardId).toBe("fireball"); + expect(state.stack?.trapped).toBe(true); + state = must(state, rig.victim, { type: "pass" }); + expect(state.players.find((p) => p.id === rig.victim)!.life).toBe(10); + expect(Object.keys(state.slimeTraps).length).toBe(0); + }); + + it("reflections off a trapped spell never touch its caster", () => { + const rig = slimeRig(); + let state = rig.state; + const fb = giveCard(state, rig.caster, "fireball"); + giveCard(state, rig.victim, "full-reflection", "FR", 0); + state = must(state, rig.caster, { + type: "cast", instanceId: fb.instanceId, target: { kind: "cell", cell: rig.slimeAt.cell }, + }); + state = must(state, rig.caster, { type: "endTurn", draw: 0 }); + state = must(state, rig.victim, { type: "move", direction: rig.slimeAt.side }); + state = must(state, rig.victim, { type: "counteract", instanceId: "full-reflection#FR" }); + state = must(state, rig.caster, { type: "pass" }); + state = must(state, rig.victim, { type: "pass" }); + expect(state.players.find((p) => p.id === rig.caster)!.life).toBe(15); + expect(state.players.find((p) => p.id === rig.victim)!.life).toBe(15); + }); + + it("a five-point waterbolt washes the slime (and its cargo) away", () => { + const rig = slimeRig(); + let state = rig.state; + const fb = giveCard(state, rig.caster, "fireball"); + state = must(state, rig.caster, { + type: "cast", instanceId: fb.instanceId, target: { kind: "cell", cell: rig.slimeAt.cell }, + }); + state = must(state, rig.caster, { type: "endTurn", draw: 0 }); + const wb = giveCard(state, rig.victim, "waterbolt", "WB", 0); + giveCard(state, rig.victim, "number-5", "N", 1); + state = must(state, rig.victim, { + type: "cast", instanceId: wb.instanceId, numberInstanceIds: ["number-5#N"], + target: { kind: "cell", cell: rig.slimeAt.cell }, + }); + expect(state.squareContents[cellKey(rig.slimeAt.cell)]).toBeUndefined(); + expect(Object.keys(state.slimeTraps).length).toBe(0); + }); +}); diff --git a/packages/engine/test/creatures.test.ts b/packages/engine/test/creatures.test.ts index 194df61..7f5903a 100644 --- a/packages/engine/test/creatures.test.ts +++ b/packages/engine/test/creatures.test.ts @@ -341,3 +341,102 @@ describe("counteracting a creature's blow (rules rev 4)", () => { expect(state.players.find((p) => p.id === rig.victim)!.life).toBe(13); }); }); + +describe("big man (rules rev 5)", () => { + function bigRig() { + let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 5 }); + state = toRound2(state); + const giant = activePlayer(state); + state.sustained.push({ + id: "fx-big", cardId: "big-man", casterId: giant.id, targetId: giant.id, + remainingTurns: 9, data: {}, + }); + return { state, giant }; + } + + /** Any square with two open steps in a straight line (test surgery spot). */ + function straightRun(state: GameState): { at: Cell; side: Side; mid: Cell; far: Cell } { + const view = boardView(state); + for (const key of Object.keys(view.cells)) { + const [x, y] = key.split(",").map(Number) as [number, number]; + const at = { x, y }; + if (view.homes.some((h) => cellKey(h) === key)) continue; + for (const side of SIDES) { + const t1 = stepTarget(view, at, side); + if (t1.kind !== "step") continue; + const t2 = stepTarget(view, t1.to, side); + if (t2.kind !== "step") continue; + return { at, side, mid: t1.to, far: t2.to }; + } + } + throw new Error("no straight corridor anywhere — impossible"); + } + + it("pushes a wizard down the corridor as he advances", () => { + const rig = bigRig(); + let state = rig.state; + const victim = state.players.find((p) => p.id !== rig.giant.id)!; + const run = straightRun(state); + const giant = state.players.find((p) => p.id === rig.giant.id)!; + giant.position = { ...run.at }; + victim.position = { ...run.mid }; + state = must(state, rig.giant.id, { type: "move", direction: run.side }); + const after = state.players.find((p) => p.id !== rig.giant.id)!; + expect(cellKey(after.position)).toBe(cellKey(run.far)); + expect(cellKey(state.players.find((p) => p.id === rig.giant.id)!.position)).toBe(cellKey(run.mid)); + }); + + it("no room to shove means no way forward", () => { + const rig = bigRig(); + const state = rig.state; + const victim = state.players.find((p) => p.id !== rig.giant.id)!; + const view = boardView(state); + // Find a step whose far side is walled: victim there cannot be pushed. + for (const side of SIDES) { + const t1 = stepTarget(view, rig.giant.position, side); + if (t1.kind !== "step") continue; + const t2 = stepTarget(view, t1.to, side); + if (t2.kind === "step") continue; + victim.position = { ...t1.to }; + const r = applyCommand(state, rig.giant.id, { type: "move", direction: side }); + expect(r.ok).toBe(false); + return; + } + // Seed offered no walled pocket beside the home; that is fine — the + // pushable case above pins the mechanism. + }); + + it("monsters cannot enter the giant's square", () => { + const rig = bigRig(); + let state = rig.state; + const view = boardView(state); + const spot = emptyNeighborCell(state, rig.giant.position); + state.creatures.push({ + id: "t1", kind: "troll", controllerId: state.players.find((p) => p.id !== rig.giant.id)!.id, + position: { ...spot.cell }, + damage: 0, maxDamage: 8, movesPerTurn: 3, movementUsed: 0, + wallPassesPerTurn: 0, wallPassUsed: 0, attackUsed: false, justCreated: false, scorchedThisTurn: [], + }); + state = must(state, rig.giant.id, { type: "endTurn", draw: 0 }); + const controller = activePlayer(state).id; + const back = (["N", "S", "E", "W"] as const).find((d) => { + const t = stepTarget(view, spot.cell, d); + return t.kind === "step" && cellKey(t.to) === cellKey(rig.giant.position); + })!; + const r = applyCommand(state, controller, { type: "moveCreature", creatureId: "t1", direction: back }); + expect(r.ok).toBe(false); + }); + + it("steps over a pit for two movement points, never entering it", () => { + const rig = bigRig(); + let state = rig.state; + const run = straightRun(state); + state.players.find((p) => p.id === rig.giant.id)!.position = { ...run.at }; + state.squareContents[cellKey(run.mid)] = { kind: "pit", damage: 0, createdBy: rig.giant.id }; + state = must(state, rig.giant.id, { type: "move", direction: run.side, over: true }); + const g = state.players.find((p) => p.id === rig.giant.id)!; + expect(cellKey(g.position)).toBe(cellKey(run.far)); + expect(state.turn.movementUsed).toBe(2); + expect(g.inPit).toBe(false); + }); +}); diff --git a/packages/server/src/rooms.ts b/packages/server/src/rooms.ts index 255f569..464267b 100644 --- a/packages/server/src/rooms.ts +++ b/packages/server/src/rooms.ts @@ -44,7 +44,7 @@ export interface Room { const rooms = new Map(); /** Rules revision new games are dealt under (stored games keep their own). */ -const RULES_REV = 4; +const RULES_REV = 5; const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index a3083e5..0e04444 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -201,6 +201,10 @@ peekCard = null; peekCreatureId = null; if (youMustRespond || youMustShield) { + if (youMustRespond && card.cardId === "teleport") { + selectedCard = card; // then click the escape square + return; + } dispatch({ type: "counteract", instanceId: card.instanceId }); return; } @@ -317,6 +321,11 @@ function clickCell(cell: { x: number; y: number }) { if (!view) return; + if (youMustRespond && selectedCard?.cardId === "teleport") { + dispatch({ type: "counteract", instanceId: selectedCard.instanceId, params: { cell } }); + clearSelection(); + return; + } if (!isYourTurn) { peekAt(cell); return; } if (pendingCellFor && selectedCard) { // Stage 2 of teleport-opponent: destination chosen. @@ -428,6 +437,21 @@ return; } } + // BIG MAN: clicking two squares away over a pit/tacks/ooze leaps it. + if (view.sustained.some((e) => e.cardId === "big-man" && e.targetId === view!.you)) { + for (const side of SIDES) { + const mid = { x: me.position.x + (side === "E" ? 1 : side === "W" ? -1 : 0), + y: me.position.y + (side === "S" ? 1 : side === "N" ? -1 : 0) }; + const far = { x: me.position.x + (side === "E" ? 2 : side === "W" ? -2 : 0), + y: me.position.y + (side === "S" ? 2 : side === "N" ? -2 : 0) }; + const hazard = view.squareContents[cellKey(mid)]?.kind; + if (cellKey(far) === cellKey(cell) && + (hazard === "pit" || hazard === "tacks" || hazard === "ooze")) { + dispatch({ type: "move", direction: side, over: true }); + return; + } + } + } // The click claimed no action — offer the square's card instead. peekAt(cell); } @@ -1162,7 +1186,9 @@ {/if} {#if youMustRespond && view.stack} - {#if view.stack.defenderId === view.you} + {#if view.stack.defenderId === view.you && selectedCard?.cardId === "teleport"} + Escaping by teleport — click a square within four spaces. + {:else if view.stack.defenderId === view.you} {view.stack.attackerId} attacks with {attackingCreature ? `the ${cardDef(attackingCreature.kind).name}` : view.stack.attackCard ? cardDef(view.stack.attackCard.cardId).name : "a punch"} — tap a counteraction card, or diff --git a/packages/web/src/local.svelte.ts b/packages/web/src/local.svelte.ts index 554c720..6c90b4a 100644 --- a/packages/web/src/local.svelte.ts +++ b/packages/web/src/local.svelte.ts @@ -101,7 +101,7 @@ class LocalGame { seed, sets: expansion ? ["basic", "expansion1"] : ["basic"], ...(colors ? { colors } : {}), - deckRev: 4, + deckRev: 5, }; const { state, events } = createGame(config); this.config = config; @@ -118,6 +118,9 @@ class LocalGame { this.tallyId = crypto.randomUUID(); this.activeMs = 0; this.lastMoveAt = Date.now(); + // iOS evicts unprotected origin storage under pressure; ask for durability + // so a saved hotseat game survives more than a forced reload. + navigator.storage?.persist?.().catch(() => {}); net.reportHotseat({ id: this.tallyId, stage: "started", players: cleaned.length }); this.persist(); return null; @@ -152,6 +155,7 @@ class LocalGame { this.gameState = current; this.active = true; this.viewerId = null; + navigator.storage?.persist?.().catch(() => {}); this.handoffTo = actorId(current); return true; } catch (e) { diff --git a/packages/web/src/net.svelte.ts b/packages/web/src/net.svelte.ts index 4e505a3..79fc517 100644 --- a/packages/web/src/net.svelte.ts +++ b/packages/web/src/net.svelte.ts @@ -145,6 +145,12 @@ export function humanize(e: GameEvent): string | null { case "objectPickedUp": return `${e.player} picks up the ${cardDef(e.card.cardId).name} — actions over.`; case "wardSet": return e.armed ? "Your ward is set — the next thief bleeds." : "Your ward stands down."; case "chaosShielded": return `${e.player} raises a FULL SHIELD and sits out the chaos.`; + case "pushed": return e.player + ? `${e.by} shoves ${e.player} down the corridor!` + : `${e.by} shoves the beast down the corridor!`; + case "spellTrapped": return `${e.caster} casts ${spellName(e.cardId)} into the slime — it sticks, waiting.`; + case "slimeTrapSprung": return `The slime releases its ${spellName(e.cardId)} at ${e.victim}!`; + case "slimeWashed": return `The wave washes the slime away.`; case "wallDamaged": { const what = e.needed === 15 ? "door" : "wall"; return `${e.player} batters the ${what} with ${e.source === "punch" ? "bare fists" : cardDef(e.source).name} — ${e.total}/${e.needed}.`;