diff options
| author | Aargh Rai <aargh.rai+git@gmail.com> | 2026-07-16 14:08:24 +0530 |
|---|---|---|
| committer | Aargh Rai <aargh.rai+git@gmail.com> | 2026-07-16 14:08:24 +0530 |
| commit | 1a52152852ff29c727673fc80e8bf8eca5ed6365 (patch) | |
| tree | e719771aa1b23f0b8774814ee907057f86f1dda1 /Plan.md | |
| parent | 6ac448727e1e26c78055eaaa361217f64393d808 (diff) | |
starting pos, move count, test pass
Diffstat (limited to 'Plan.md')
| -rw-r--r-- | Plan.md | 100 |
1 files changed, 100 insertions, 0 deletions
@@ -0,0 +1,100 @@ +# Gacrux: Pseudo-Legal to Legal Move Conversion Plan + +## Current State +- Move generators produce **pseudo-legal** moves (follow piece movement rules but ignore checks/pins) +- `position_make_move()` has multiple bugs +- No attack detection or legal move filtering exists +- Perft test conditions are inverted + +## Phase 1: Fix Existing Bugs + +### `src/engine/moves.c` — `position_make_move()` +1. **Lines 94, 100**: Add missing semicolons after `assert(0)` +2. **Lines 54-55**: Black long castle modifies `WHITE_ROOK` → should be `BLACK_ROOK` +3. **Lines 71-73**: Black short castle modifies `WHITE_KING`/`WHITE_ROOK` → should be `BLACK_KING`/`BLACK_ROOK` +4. **Lines 96, 98**: `move.from` (u8) compared against `u64` bitboard constants → use square indices 63 and 56 +5. **Missing turn toggle**: `position->turn` never flips after a move + +### `test_perft_starting_position` +6. **Lines 169-176**: `==` → `!=` (test currently passes when counts are wrong) + +## Phase 2: Attack Detection + +Create `src/engine/moves/attack.c` with: +```c +bool square_attacked(position_t position, square_t square, u8 by_color); +``` + +Checks if any piece of `by_color` attacks `square`: +- **Pawn attacks**: Check diagonally forward for enemy pawns +- **Knight attacks**: Use existing `knight_moves[64]` lookup +- **King attacks**: Use existing `920078ULL` pattern +- **Sliding attacks**: Walk rays from target square (for now, ray-walking) + +## Phase 3: Legal Move Filtering + +Modify `get_moves()` (or add `get_legal_moves()`) to: +1. Generate all pseudo-legal moves +2. For each move, make it on a copy of the position +3. Find the friendly king in the resulting position +4. Check if the friendly king is attacked by the opponent +5. If attacked → remove the move (swap with last, decrement length) + +**Castling pre-checks** (in `king.c`): +- King not currently in check +- Squares king passes through are not attacked +- No pieces between king and rook + +## Phase 4: Magic Bitboards (Performance Optimization) + +Replace ray-walking with O(1) magic lookups for sliding piece attacks. + +### Data Structure +```c +typedef struct { + bitboard_t mask; // relevant occupancy bits (excludes edges) + bitboard_t *attacks; // pointer into attack table + bitboard_t magic; // the magic multiplier + int shift; // 64 - popcount(mask) +} magic_t; +``` + +### Attack Tables +- `rook_attacks[0x19000]` (~100KB) +- `bishop_attacks[0x1480]` (~5KB) + +### Index Computation +```c +unsigned index = ((occupied & magic.mask) * magic.magic) >> magic.shift; +return magic.attacks[index]; +``` + +### Initialization +At startup, for each square: +1. Compute mask (pseudo-attacks minus edges) +2. Enumerate all subsets of the mask +3. Compute true attacks via ray-walking (reference) +4. Find magic number via PRNG search + +### Usage +```c +// Move generation +bitboard_t attacks = get_rook_attacks(from_square, all_occupied); +attacks &= ~friendly_pieces; + +// Attack detection +bool square_attacked(...) { + if (get_rook_attacks(sq, occ) & (enemy_rooks | enemy_queens)) return true; + if (get_bishop_attacks(sq, occ) & (enemy_bishops | enemy_queens)) return true; + // ... +} +``` + +## Implementation Order +1. Fix bugs (Phase 1) +2. Add `square_attacked()` with ray-walking +3. Add legal move filtering +4. Fix castling legality +5. Verify perft d0-d7 +6. Uncomment + verify Kiwipete perft +7. Add magic bitboards (Phase 4) |
