From 6ffa9488eeadf12aa30a97fba375743657aee442 Mon Sep 17 00:00:00 2001 From: Andy Kurnia Date: Mon, 30 Mar 2026 08:04:16 +0800 Subject: [PATCH] fix shadow play: only use ahead extension set per direction Shadow play was ANDing both left and right extension sets at every position. The "behind" extension set (based on board tiles already traversed) can be more restrictive than the GADDAG allows, because the extension set doesn't account for rack tiles placed during word generation. This made the shadow play upper bound too tight, potentially missing valid moves. Fix: shadow_play_right uses only left_extension_strip (ahead). shadow_play_left uses only right_extension_strip (ahead). Word generation already uses one extension set per direction. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/movegen-test-baseline.txt | 36 +++++++++++++++++------------------ src/movegen.rs | 12 ++++-------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/movegen-test-baseline.txt b/src/movegen-test-baseline.txt index 495a68c..0619d56 100644 --- a/src/movegen-test-baseline.txt +++ b/src/movegen-test-baseline.txt @@ -191,32 +191,32 @@ 13.0460 9B ROTOR 13 === Case 11: rack=ST fen=ZONULE1B2APAID/1KY2RHANJA4/GAM4R2HUI2/7G6D/6FECIT3O/6AE1TOWIES/6I7E/1EnGUARD6D/NAOI2W8/6AT7/5PYE7/5L1L7/2COVE1L7/5X1E7/7N7 === 18.1100 11F (PYE)T 9 - 14.1100 B8 (EA)T 5 14.1100 6F T(AE) 5 + 14.1100 B8 (EA)T 5 14.0000 I10 ST 14 - 13.1100 7N T(E) 4 + 13.1100 7F T(I) 4 13.1100 L3 (U)T 4 - 13.1100 5N T(O) 4 - 13.1100 D12 T(O) 4 + 13.1100 7N T(E) 4 13.1100 N1 (I)T 4 + 13.1100 D12 T(O) 4 13.1100 D8 (GI)T 4 - 13.1100 7F T(I) 4 + 13.1100 5N T(O) 4 13.0000 N1 (I)TS 13 12.1100 K5 (TO)T 3 12.1100 B7 T(EA) 3 - 12.1100 M6 (I)T 3 + 12.1100 10G (AT)T 3 === Case 12: rack=OO fen=ZONULE1B2APAID/1KY2RHANJA4/GAM4R2HUI2/7G6D/6FECIT3O/6AE1TOWIES/6I7E/1EnGUARD6D/NAOI2W8/6AT7/5PYE7/5L1L7/2COVE1L7/5X1E7/7N7 === - 11.0000 2N OO 11 11.0000 7L OO 11 + 11.0000 2N OO 11 10.0000 7K OO 10 8.0000 I12 OO 8 8.0000 10C OO 8 7.0000 L6 (W)OO 7 6.5220 3G O(R) 9 6.0000 10D OO 6 - 5.0000 N8 OO 5 - 5.0000 C13 (C)OO 5 5.0000 A3 (G)OO 5 + 5.0000 C13 (C)OO 5 + 5.0000 N8 OO 5 4.0000 13H (L)OO 4 3.5220 1H (B)O 6 3.0000 A9 (N)OO 3 @@ -230,9 +230,9 @@ 7.0000 L6 (W)OO 7 6.5220 3G O(R) 9 6.0000 10D OO 6 + 5.0000 A3 (G)OO 5 5.0000 N8 OO 5 5.0000 C13 (C)OO 5 - 5.0000 A3 (G)OO 5 4.0000 13H (L)OO 4 3.5220 1H (B)O 6 3.0000 15H (N)OO 3 @@ -247,31 +247,31 @@ 1.5220 N1 (I)O 4 0.5220 O1 (D)O 3 0.5220 M6 (I)O 3 - 0.5220 13H (L)O 3 - 0.5220 8N O(D) 3 0.5220 C7 O(nO) 3 + 0.5220 8N O(D) 3 + 0.5220 13H (L)O 3 0.5220 K5 (TO)O 3 0.5220 4N O(D) 3 0.0000 Exch. OO -0.4780 15G O(N) 2 -0.4780 12H (L)O 2 -0.4780 15H (N)O 2 - -0.4780 M3 (I)O 2 + -0.4780 M5 O(I) 2 -0.4780 D13 (O)O 2 - -0.4780 C8 (nO)O 2 + -0.4780 M3 (I)O 2 -0.4780 A9 (N)O 2 -0.4780 E7 O(U) 2 - -0.4780 M5 O(I) 2 + -0.4780 C8 (nO)O 2 -2.4780 Exch. O -9.6960 Pass === Case 14: rack=VUAENRU fen=5MOZ6S/2FIREPOTS4p/2Y2UTA1HWAN1E/DAK8L2C/OWE1BIB4E2I/CADGE6U1PA/I4DOGY2R1aT/L2GLORIA1LOVIE/E1XI1TAED2N1N1/RAI8S1T1/13I1/13E1/13R1/15/15 === - 17.4770 M13 UVA 16 17.4770 12L UV(E)A 16 + 17.4770 M13 UVA 16 16.8870 12H UNREAV(E) 22 - 15.4770 5I UVA(E) 14 - 15.4770 K10 UVA 14 15.4770 5J UV(E)A 14 + 15.4770 K10 UVA 14 15.4770 12K UVA(E) 14 + 15.4770 5I UVA(E) 14 14.4390 12I AVENU(E) 18 12.8570 K10 URVA 16 12.4770 1L VAU(S) 11 diff --git a/src/movegen.rs b/src/movegen.rs index 1318a84..c861cab 100644 --- a/src/movegen.rs +++ b/src/movegen.rs @@ -1240,10 +1240,8 @@ fn gen_place_placements<'a, PossibleStripPlacementCallbackType: FnMut(i8, i8, i8 } else if this_cross_bits != 1 { // something hooks here and there is a valid letter. // this_cross_bits has bit 1 set, so blank is always allowed. - let matching_bits = this_cross_bits - & rack_bits - & env.params.left_extension_strip[idx as usize] - & env.params.right_extension_strip[idx as usize]; + let matching_bits = + this_cross_bits & rack_bits & env.params.left_extension_strip[idx as usize]; if matching_bits == 0 { break; } @@ -1348,10 +1346,8 @@ fn gen_place_placements<'a, PossibleStripPlacementCallbackType: FnMut(i8, i8, i8 } else if this_cross_bits != 1 { // something hooks here and there is a valid letter. // this_cross_bits has bit 1 set, so blank is always allowed. - let matching_bits = this_cross_bits - & rack_bits - & env.params.left_extension_strip[idx as usize] - & env.params.right_extension_strip[idx as usize]; + let matching_bits = + this_cross_bits & rack_bits & env.params.right_extension_strip[idx as usize]; if matching_bits == 0 { break; }