Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b2468ab
Avoid bulk zero-initialization of move buffers
KnightStackSys Oct 12, 2026
61b0db8
Add fast tactical move generation
KnightStackSys Oct 12, 2026
b7f1b71
Generate qsearch tactical moves directly
KnightStackSys Oct 12, 2026
58bf460
Add fast undo primitives for position updates
KnightStackSys Oct 12, 2026
e59949c
Speed up position attack checks and undo
KnightStackSys Oct 12, 2026
4a61776
Throttle expensive wall-clock checks in search
KnightStackSys Oct 12, 2026
d78f61e
Optimize search hot paths for NPS
KnightStackSys Oct 12, 2026
63e3f71
Initialize throttled timer checks for MultiPV
KnightStackSys Oct 12, 2026
a50b8a9
Reduce SEE scratch and capture lookup overhead
KnightStackSys Oct 12, 2026
d985097
Avoid redundant TT probe initialization
KnightStackSys Oct 12, 2026
597398d
Avoid clearing TT probe output on misses
KnightStackSys Oct 12, 2026
e7dda13
Cache move ordering metadata in search
KnightStackSys Oct 12, 2026
24a63f7
Reuse move ordering metadata in the main search loop
KnightStackSys Oct 12, 2026
d87a646
Add compact line attack table interfaces
KnightStackSys Oct 12, 2026
69925eb
Build 14-bit sliding line lookup tables
KnightStackSys Oct 12, 2026
5ce47d3
Maintain compact line occupancy in Position
KnightStackSys Oct 12, 2026
40dc77e
Use compact line occupancy for current attacks
KnightStackSys Oct 12, 2026
79b4861
Use line lookup attacks in move generation
KnightStackSys Oct 12, 2026
b9fe4fe
Use line lookup attacks in HCE mobility
KnightStackSys Oct 12, 2026
cfb7575
Cross-check line attacks against ray attacks
KnightStackSys Oct 12, 2026
d55bd6c
Benchmark ray and compact line slider attacks
KnightStackSys Oct 12, 2026
92d72fc
Use lazy move selection and cheap capture ordering
KnightStackSys Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions include/ironphoenix/geometry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@

namespace ironphoenix::Geometry {

inline constexpr unsigned LINE_BITS = 14;
inline constexpr unsigned LINE_OCCUPANCIES = 1u << LINE_BITS;
inline constexpr unsigned LINE_HALF_BITS = 7;
inline constexpr unsigned LINE_HALF_OCCUPANCIES = 1u << LINE_HALF_BITS;

inline constexpr unsigned LINE_RANK = 0;
inline constexpr unsigned LINE_FILE = 1;
inline constexpr unsigned LINE_DIAG_A = 2; // file - rank
inline constexpr unsigned LINE_DIAG_B = 3; // file + rank
inline constexpr unsigned LINE_FAMILY_NB = 4;

inline constexpr unsigned RANK_LINE_BASE = 0;
inline constexpr unsigned FILE_LINE_BASE = RANK_LINE_BASE + BOARD_RANKS;
inline constexpr unsigned DIAG_A_LINE_BASE = FILE_LINE_BASE + BOARD_FILES;
inline constexpr unsigned DIAG_B_LINE_BASE = DIAG_A_LINE_BASE + 27;
inline constexpr unsigned LINE_NB = DIAG_B_LINE_BASE + 27;

extern std::array<std::array<Square, BOARD_RANKS>, BOARD_FILES> SquareFromXY;
extern std::array<std::uint8_t, SQUARE_NB> FileOf;
extern std::array<std::uint8_t, SQUARE_NB> RankOf;
Expand All @@ -18,6 +35,16 @@ extern std::array<std::array<Bitboard, SQUARE_NB>, COLOR_NB> PawnAttacks;
extern std::array<std::array<Bitboard, SQUARE_NB>, COLOR_NB> PawnAttackers;
extern Bitboard BoardMask;

// Compact 14-bit line representation used by Position for current-occupancy
// rook/bishop/queen attacks. Missing cross-board squares are encoded as
// permanent blockers so a diagonal can never jump across a board hole.
extern std::array<std::array<std::uint8_t, SQUARE_NB>, LINE_FAMILY_NB> LineId;
extern std::array<std::array<std::uint8_t, SQUARE_NB>, LINE_FAMILY_NB> LinePos;
extern std::array<std::array<std::uint16_t, SQUARE_NB>, LINE_FAMILY_NB> LineBit;
extern std::array<std::uint16_t, LINE_NB> LineBlockers;
extern std::array<std::array<std::uint16_t, LINE_OCCUPANCIES>, LINE_BITS> LineAttackMask;
extern std::array<std::array<std::array<Bitboard, LINE_HALF_OCCUPANCIES>, 2>, LINE_NB> LineExpand;

void init();

IRONPHOENIX_FORCE_INLINE constexpr bool validXY(int file, int rank) noexcept {
Expand Down Expand Up @@ -86,6 +113,16 @@ IRONPHOENIX_FORCE_INLINE bool isPawnHomeSquare(Color c, Square sq) noexcept {
return false;
}

IRONPHOENIX_FORCE_INLINE Bitboard expandLine(unsigned line, std::uint16_t mask) noexcept {
return LineExpand[line][0][mask & (LINE_HALF_OCCUPANCIES - 1u)]
| LineExpand[line][1][mask >> LINE_HALF_BITS];
}

IRONPHOENIX_FORCE_INLINE Bitboard lineAttacks(unsigned line, unsigned pos, std::uint16_t occupancy) noexcept {
const std::uint16_t attacks = LineAttackMask[pos][occupancy & (LINE_OCCUPANCIES - 1u)];
return expandLine(line, attacks);
}

IRONPHOENIX_FORCE_INLINE constexpr bool rayUsesLsb(Direction d) noexcept {
return d == NORTH || d == EAST || d == NORTH_EAST || d == NORTH_WEST;
}
Expand All @@ -100,6 +137,8 @@ IRONPHOENIX_FORCE_INLINE Bitboard rayAttack(Square sq, Direction d, Bitboard occ
return ray ^ Ray[static_cast<unsigned>(d)][blocker];
}

// Generic attacks for hypothetical occupancies (SEE, givesCheck, castling).
// Current-position move generation uses Position's compact line occupancy path.
IRONPHOENIX_FORCE_INLINE Bitboard rookAttacks(Square sq, Bitboard occ) noexcept {
return rayAttack(sq, NORTH, occ)
| rayAttack(sq, EAST, occ)
Expand Down
5 changes: 4 additions & 1 deletion include/ironphoenix/move.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ enum Promotion : std::uint8_t {
};

struct Move {
std::uint32_t v = 0;
// Intentionally no default member initializer. Plain local arrays of Move
// can stay uninitialized, avoiding large memset-style clears in every
// search node. Value-initialized Move{} still produces the null move.
std::uint32_t v;

constexpr Move() noexcept = default;
constexpr explicit Move(std::uint32_t raw) noexcept : v(raw) {}
Expand Down
5 changes: 4 additions & 1 deletion include/ironphoenix/movegen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ namespace ironphoenix {
constexpr std::size_t MAX_MOVES = 512;

struct MoveList {
std::array<Move, MAX_MOVES> moves{};
// Deliberately left uninitialized. Only entries below size are ever read.
// This avoids clearing 2 KiB of move storage every time a MoveList is made.
std::array<Move, MAX_MOVES> moves;
std::size_t size = 0;

IRONPHOENIX_FORCE_INLINE void clear() noexcept { size = 0; }
Expand All @@ -27,6 +29,7 @@ struct MoveList {
};

void generatePseudoLegalMoves(const Position& pos, MoveList& out);
void generateTacticalMoves(const Position& pos, MoveList& out);
void generateLegalMoves(Position& pos, MoveList& out);

[[nodiscard]] bool isTerminalKingCapture(const Position& pos, Move m) noexcept;
Expand Down
63 changes: 63 additions & 0 deletions include/ironphoenix/position.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,39 @@ class Position {
return (aliveMask_ & (1u << static_cast<unsigned>(c))) != 0;
}

// Fast current-occupancy slider attacks. Position maintains a compact
// 14-bit occupancy for every rank/file/diagonal, so these are table lookups
// rather than three-word ray scans.
[[nodiscard]] IRONPHOENIX_FORCE_INLINE Bitboard rookAttacks(Square sq) const noexcept {
const unsigned rankLine = Geometry::LineId[Geometry::LINE_RANK][sq];
const unsigned fileLine = Geometry::LineId[Geometry::LINE_FILE][sq];
return Geometry::lineAttacks(
rankLine,
Geometry::LinePos[Geometry::LINE_RANK][sq],
lineOcc_[rankLine])
| Geometry::lineAttacks(
fileLine,
Geometry::LinePos[Geometry::LINE_FILE][sq],
lineOcc_[fileLine]);
}

[[nodiscard]] IRONPHOENIX_FORCE_INLINE Bitboard bishopAttacks(Square sq) const noexcept {
const unsigned diagA = Geometry::LineId[Geometry::LINE_DIAG_A][sq];
const unsigned diagB = Geometry::LineId[Geometry::LINE_DIAG_B][sq];
return Geometry::lineAttacks(
diagA,
Geometry::LinePos[Geometry::LINE_DIAG_A][sq],
lineOcc_[diagA])
| Geometry::lineAttacks(
diagB,
Geometry::LinePos[Geometry::LINE_DIAG_B][sq],
lineOcc_[diagB]);
}

[[nodiscard]] IRONPHOENIX_FORCE_INLINE Bitboard queenAttacks(Square sq) const noexcept {
return rookAttacks(sq) | bishopAttacks(sq);
}

[[nodiscard]] Bitboard attackersTo(Square target, Color by, Bitboard occ) const noexcept;
[[nodiscard]] Bitboard attackersToEnemy(Square target, Color victim, Bitboard occ) const noexcept;
[[nodiscard]] bool isAttackedBy(Square target, Color by, Bitboard occ) const noexcept;
Expand Down Expand Up @@ -104,6 +137,10 @@ class Position {
std::array<std::array<Bitboard, PIECE_TYPE_NB>, COLOR_NB> pieceBB_{};
std::array<Bitboard, COLOR_NB> colorOcc_{};
Bitboard allOcc_{};

// Includes permanent blocker bits for the missing cross-board corner cells.
std::array<std::uint16_t, Geometry::LINE_NB> lineOcc_{};

std::array<Square, COLOR_NB> kingSq_{SQ_NONE, SQ_NONE, SQ_NONE, SQ_NONE};
std::array<Square, COLOR_NB> enPassant_{SQ_NONE, SQ_NONE, SQ_NONE, SQ_NONE};
std::array<std::array<CastleLane, 2>, COLOR_NB> castle_{};
Expand All @@ -115,8 +152,34 @@ class Position {
Key key_ = 0;
Bitboard checkers_{};

IRONPHOENIX_FORCE_INLINE void addLineOccupancy(Square sq) noexcept {
lineOcc_[Geometry::LineId[Geometry::LINE_RANK][sq]] |= Geometry::LineBit[Geometry::LINE_RANK][sq];
lineOcc_[Geometry::LineId[Geometry::LINE_FILE][sq]] |= Geometry::LineBit[Geometry::LINE_FILE][sq];
lineOcc_[Geometry::LineId[Geometry::LINE_DIAG_A][sq]] |= Geometry::LineBit[Geometry::LINE_DIAG_A][sq];
lineOcc_[Geometry::LineId[Geometry::LINE_DIAG_B][sq]] |= Geometry::LineBit[Geometry::LINE_DIAG_B][sq];
}

IRONPHOENIX_FORCE_INLINE void removeLineOccupancy(Square sq) noexcept {
lineOcc_[Geometry::LineId[Geometry::LINE_RANK][sq]] &= static_cast<std::uint16_t>(~Geometry::LineBit[Geometry::LINE_RANK][sq]);
lineOcc_[Geometry::LineId[Geometry::LINE_FILE][sq]] &= static_cast<std::uint16_t>(~Geometry::LineBit[Geometry::LINE_FILE][sq]);
lineOcc_[Geometry::LineId[Geometry::LINE_DIAG_A][sq]] &= static_cast<std::uint16_t>(~Geometry::LineBit[Geometry::LINE_DIAG_A][sq]);
lineOcc_[Geometry::LineId[Geometry::LINE_DIAG_B][sq]] &= static_cast<std::uint16_t>(~Geometry::LineBit[Geometry::LINE_DIAG_B][sq]);
}

void rawPut(Square sq, Piece p) noexcept;
Piece rawRemove(Square sq) noexcept;

// Undo restores the complete reversible state from StateInfo afterwards,
// so these variants update only board/bitboards/occupancy and intentionally
// skip Zobrist and king-square maintenance.
void rawPutNoState(Square sq, Piece p) noexcept;
Piece rawRemoveNoState(Square sq) noexcept;

[[nodiscard]] Bitboard attackersToCurrent(Square target, Color by) const noexcept;
[[nodiscard]] Bitboard attackersToEnemyCurrent(Square target, Color victim) const noexcept;
[[nodiscard]] bool isAttackedByCurrent(Square target, Color by) const noexcept;
[[nodiscard]] bool isAttackedByEnemyCurrent(Square target, Color victim) const noexcept;

void refreshCheckers() noexcept;
void updateCastlingRightsForMove(Piece moving, Square from, Piece captured, Square capturedSq) noexcept;
};
Expand Down
12 changes: 10 additions & 2 deletions include/ironphoenix/search.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ namespace ironphoenix {
static constexpr int INF = 32000;
static constexpr int MATE_SCORE = 30000;
static constexpr int MATE_THRESHOLD = 29000;
static constexpr std::uint64_t TIME_CHECK_NODE_INTERVAL = 1024;

TranspositionTable tt_{ 64 };
HistoryTables history_{};
Expand All @@ -75,6 +76,7 @@ namespace ironphoenix {
std::chrono::steady_clock::time_point startTime_{};
std::int64_t timeBudgetMs_ = 0;
std::uint64_t nodes_ = 0;
std::uint64_t nextTimeCheckNode_ = 0;
int selDepth_ = 0;

std::array<std::array<Move, MAX_PLY>, MAX_PLY> pv_{};
Expand All @@ -88,8 +90,14 @@ namespace ironphoenix {
int negamax(Position& pos, int depth, int alpha, int beta, int ply, bool pvNode, bool allowNull = true);
int qsearch(Position& pos, int alpha, int beta, int ply, bool pvNode);
int evaluate(const Position& pos) const noexcept;
int moveScore(const Position& pos, Move move, Move ttMove, int ply) const noexcept;
void orderMoves(const Position& pos, Move* begin, Move* end, Move ttMove, int ply) const;
int moveScore(const Position& pos, Move move, Move ttMove, int ply, bool givesCheck) const noexcept;
void orderMoves(const Position& pos,
Move* begin,
Move* end,
Move ttMove,
int ply,
int* orderedScores = nullptr,
std::uint64_t* givesCheckBits = nullptr) const;

[[nodiscard]] int historyBonus(int depth) const noexcept;
void updateQuietCutoff(const Position& pos, Color mover, Move cutoffMove, Piece movingPiece,
Expand Down
14 changes: 8 additions & 6 deletions include/ironphoenix/tt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ enum class TTBound : std::uint8_t {
};

struct TTProbe {
bool hit = false;
Move move{};
int score = 0;
int depth = -1;
TTBound bound = TTBound::None;
bool pvNode = false;
// Probe fills every field on a hit. On a miss callers use the returned bool
// and must not inspect this object, so leave plain local instances untouched.
bool hit;
Move move;
int score;
int depth;
TTBound bound;
bool pvNode;
};

class TranspositionTable {
Expand Down
7 changes: 3 additions & 4 deletions src/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ namespace ironphoenix::Eval {
}

int mobilityForColor(const Position& pos, Color c) noexcept {
const Bitboard occ = pos.occupancy();
const Bitboard blockedByTeam = teamOccupancy(pos, c);

int score = 0;
Expand All @@ -68,13 +67,13 @@ namespace ironphoenix::Eval {
attacks = Geometry::KnightAttacks[sq];
break;
case BISHOP:
attacks = Geometry::bishopAttacks(sq, occ);
attacks = pos.bishopAttacks(sq);
break;
case ROOK:
attacks = Geometry::rookAttacks(sq, occ);
attacks = pos.rookAttacks(sq);
break;
case QUEEN:
attacks = Geometry::queenAttacks(sq, occ);
attacks = pos.queenAttacks(sq);
break;
default:
break;
Expand Down
Loading
Loading