Releases: lynx-chess/Lynx
v1.8.0
🔍 Search
- Improving: LMP (#1129)
- Improving: RFP (#1130, #1133)
- Improving: LMR (#1135)
- NMP, tweak reduction using eval - beta (#1139)
- NMP: Use the right score for TT condition (#1268)
- LMR: reduce more on cutnode (#1233)
- LMR: avoid when being checkmated (#1231)
- LMR: increase pv min moves 2 (#1230)
- History:
History_BestScoreBetaMargin
80 -> 60 (#1118) - History: increase bonus when best score is over beta by some margin (#1110)
- History: increase bonus when static eval is lower than alpha (#1123)
- Save static eval in TT (#1084, #1085)
- Aspiration windows: fail high reduction (#800, #1285)
⌛ Time management
- Add node time management (#1203, #1206)
- Add best move stability (#1211)
- Add score stability (#1223)
- Soft limit <= hard limit (#1210)
⚡ Speedups
- Move to .NET 9 (#1108)
- Don't attempt continuation history on root moves (#1065)
- Use
Unsafe.Add
forPSQT()
(#1153) - Make
TaperedEvaluationTerm
fields constant (#1174) - Make TT (wrapper) a readonly struct (#1200)
- Flatten killer moves array (and stack-allocate it) (#1247)
- Minimal speedup in
go
UCI command parsing (#1264)
🐛 Bug fixes
- Clear
PlyStackEntry
shared array on return (#1182) - Don't allow the search to stop if no best move is found (#1251)
- IDDFS finishes some depths with no moves (#1266)
- Incorrect (too negative) mate scores when being checkmated (#1271)
- Make sure
staticEval
var always gets initialized as part of the search (#1272) - Prevent aspiration windows to go outside of [MinEval, MaxEval] after window overflow (#1275)
🟰 Multithreaded search
-
Add support for multi-threaded search: basic lazy SMP implementation (#1263)
Threads # 1 2 4 8 ELO @ 8+0.08 - +100.96 +195.65 +263.42 ELO @ 40+0.4 - +83.35 +167.44 +220.54 NPS 1.21 Mnps 2.47 Mnps 4.88 Mnps 10.07 Mnps More detailed results can be found here.
Non strength-winning changes
- Allow non-power of two Hash sizes, implemented via 'fixed-point multiplication trick' (#1072)
- Improve and standardize
nps
reporting (#1081) - Use the total max ply as
selfdepth
value instead of last search's max ply (#1289) - Make
bench
quiet by default, and addverbosebench
(#1286) - Various big refactorings to accommodate multithreaded search (#1147, #1184, #1201, #1262)
Full Changelog: v1.7.0...v1.8.0
v1.7.0
🔍 Search
- Regular search: fail hard -> fail soft (#1039, #1040, #1041)
- QSearch: fail hard -> fail soft (#1052)
- Fail soft TT cutoffs (#1044)
- Remove
pvNode
condition for first move full search (#1045) - History pruning: quiet history (#972)
- Improve queen promotion with capture move ordering (#1061)
- Make
TranspositionTableElement.Key
anushort
instead of ashort
(#1070) - SPSA 2024-10-1 (#1074)
⚖️ Evaluation
- Enemy king related PSQTs (#924)
- Friendly and enemy king distance to passed pawn (#955)
- Passed pawns: bonus for not enemy pieces ahead (#998, #1008)
- Pawn phalanx (#1009, #1010)
- Bishop penalty: same color pawns (#1022)
- Bishop penalty: blocked central pawns (#1029)
- Checks (#1027)
- Mobility: exclude squares attacked by opponent's pawn (#958)
- Add 50 moves rule scaling, down to 50% of the score (#965)
- Improve endgame scaling with pawn count (#928)
- Bucketed passed pawns (#945)
- Index queen mobility bonus by attacks count excluding own pieces (#774)
- Tuning: use some Ethereal FRC data (#916)
- Tuning: tune at 5k epochs (50k epochs -> 10k epochs -> 5k) epochs (#1031)
⌛ Time management
- Use expected moves to go (#996)
⚡ Speedups
- Move
Move
serialization toWriter
thread (#999) - Add a
Board
array toPosition
to track where pieces are indexed by square (#849) - Remove good old
_isFollowingPV
and_isScoringPV
(#1034) - Make
TaperedEvaluationTerm
an integer I (#935) - Add PSQT class to store
PackedPSQT
(#939) - Flatten PSQTs [][][][] (#927)
- Flatten capture history (#870)
- Pin 1 dimension arrays (#953)
- Pin attack-related arrays (#985)
- Reverse killer moves arrays (#861)
- Replace Chebyshev distance calculation with lookup table (double array) (#957)
- Refactor additional evaluations: pass piece side (#963)
- Speedup
InfoCommand.SearchResultInfo
(#984) - Make
SearchResult.Moves
an array and optimize its population (#986) - Refactor
Game.PositionHashHistory
into a private array (#991) - Remove
Position.MakeMoveCalculatingCapturedPiece
, usingPosition.Board
instead (#1021) - Only update PV table on PV nodes (#1042)
🧠 Memory usage (!)
- Remove unnecessary, initial TT initializations (#989)
- Allocate TT only once, clearing it afterwards on
ucinewgame
(#990) - Avoid
static readonly
flat array initial allocations for inline arrays (#948) - Use
ArrayPool
to reduce recurrent allocations (#983) - Stop generating
logs/log-∗.log
files by default (#1004) - Cache
Move.UCIString
results in aDictionary
(#1001) - Optimize
go
command parsing (#1005) - Remove unused props from
SearchResult
and re-order the ones left (#1006)
🐛 Bug fixes
- Don't prune moves in regular search while being checkmated (#1060)
- Prevent negative checkmate scores from being lower than
EvaluationConstants.MinEval
(#1063)
Non-strength winning changes:
Relevant for testers:
- By default log files are no longer generated under
logs/
dir unless warnings or errors happen (#1004) - Lynx process' memory usage won't skyrocket to twice the expected (TT) value anymore, as it could briefly happen in the past, providing there was such memory available for it.
- Simplify
appsettings.json
(#1075)
Relevant for developers that consume the NuGet package:
- Lynx allocates way less than before when searching, which implies much lower GC pressure.
- You have control now of UCI
info
andbestmove
string allocations if you're usingSearcher
class to interact with the engine, since now the channel doesn't send the information pre-serialized (details below). - API changes:
Channel<string>
->Channel<object>
, you're now expected to invoke.ToString()
on whatever comes from the channel to print it. Alternatively, you can just consume it by checking object types (they are either strings,Lynx.Model.SearchResult
orLynx.UCI.Commands.Engine.BestMoveCommand
) (#999)- Remove
Position(Position, Move)
constructors, now you're forced to useMakeMove
/UnMakeMove
methods (#976) - Remove parameterless
Game
constructor, a fen or a parsing result is always required now (tip:Constants.InitialPositionFEN
can be used) (#980) - Make
Position.UniqueIdentifier
anulong
instead of along
(#1078)
Full Changelog: v1.6.0...v1.7.0
v1.6.0
- 🔍 Countermoves (#859)
- 🔍 Continuation history - countermove history (1 ply) (#645)
- 🔍 Don't always stop search when a mate is found (#827)
- 🔍 SPSA 2024-6-27 (#839)
- ⚖️ King-bucketed PSQTs (#873 (2) -> #876 (8) -> #879 + #888 (16) -> #893 (24) -> #902 (23))
- ⚖️ Escale endgame eval with pawn count (#821, #829)
- ⚖️ Give bonus to pieces protected by friendly pawns and penalty to pieces attacked by opponent pawns (#830)
- ⚖️ Use some Pedantic data for HCE tuning (#905)
- ⚡ Avoid
stackalloc
local initialization when allocating it for movegen (#858) - ⚡ Optimize
MoveGenerator.GeneratePieceCaptures()
(#846) - ⚡ Micro-optimization in
MoveGenerator.IsAnyPieceMoveValid
(#845)
Non strength-winning changes:
- ⚙️ Move eval parameters out of
Configuration
class, making them no longer configurable viaappsettings.json
(#889, #911) - 🐛 Make
.ToEPDString()
fully PGN/EPD compliant (#841) - 🐛 Avoid node count overflow (#835)
Full Changelog: v1.5.1...v1.6.0
v1.5.1
Full Changelog: v1.5.0...v1.5.1
v1.5.0
- 🔍 Add Futility pruning (FP) (#733)
- 🔍 LMR: allow when in check (#702)
- 🔍 LMR: reduce more if there's a TT move and a capture (#706)
- 🔍 Move RFP before NMP (#732)
- 🔍 SPSA search parameters tuning (#730, #764)
- ⚖️ Remove double pawns penalty [proper SPRT pawn eval] (#746)
- ⚖️ Index bishop mobility bonus by attacks count (#758)
- ⚖️ Index rook mobility bonus by attacks count exluding own pieces (#768)
- ⚖️ Add knight mobility bonus and index it by attacks count excluding own pieces (#775)
- ⚖️ Take only pawns into account for king shield (#789)
- ⚖️ Add king virtual mobility indexed by mobility count (#785)
- ⚖️ Use some Stoofvlees quiet data for eval tuning (#710)
- ⚡ Improve search logic (#725)
- ⚡ Use optimized method to check if a move was valid (#716)
- ⚡ Use a
StringBuilder
to generate UCIinfo
command (#805) - ⚡ Remove option to disable TT (#720)
- ⚡ Remove manual piece count during static eval (#698)
- ⚡ Simplify
TaperedEvaluationTermByRank
(#757) - ⚡ Simplify
TaperedEvaluationTermByCount
(#767) - ⚡ Simplify Aspiration windows (#802)
- 🐛 Fix index out of range exception on max depth (#708)
- 🐛 Detect threefold repetition on
pvNode
(#796) - 🐛 Fix
Game.MakeMove
behavior on invalid moves (#804)
Non strength-winning changes:
- Add ponder support (#772)
- Generate UCI options for search parameters dynamically (#734)
- Normalize mobility values (#788)
Full Changelog: v1.4.0...v1.5.0
v1.4.0
- 🔍 Improve RFP (#652)
- 🔍 Avoid doing TT cutoffs on PV nodes (#653)
- 🔍 Use TT score as positional eval for pruning (#692)
- ⚖ Tweak pawnless endgames evaluation (#693)
- ⌛ Tweak time management (#664, #665, #667, #668, #671, #677, #691)
- ⚡ Stop checking for two/threefold repetition and 50 moves draws in QSearch (#673)
- ⚡ Refactor
Update50movesRule()
method (#678) - ⚡ Reimplement repetition detection (#679)
- ⚡ Prefetch TT entry in NegaMax search (#681)
- ⚡ Remove
Position.StaticEval()
heap allocations (#683) - ⚡ Force GC collection at the end of
Engine
constructor and afterucinewgame
(#685) - ⚡ Use packed evaluation (#697)
- 🐛 Clear history on
newgame
(#649) - 🐛 Fix long input
position
commands parsing (#650) - 🐛 Fix engine stall when depth over 100 is reached during search (#651)
- 🐛 Don't search with fixed depth when cutechess provides 0s to move (#654)
- 🐛 Prevent illegal moves when low in time (#657)
- 🐛 Error when searching at max depth (#670)
Non strength-winning changes:
- Add
fen
UCI command (#688 - Increase max TT size from 1GB to 8GB (#669)
- 🐛 Fix behavior of consecutive
go
commands (#655)
Full Changelog: v1.3.0...v1.4.0
v1.3.0
- 🔍 Add basic (quiet) history malus/penalty (#610)
- 🔍 Add capture history (#634)
- 🔍 Update (quiet) history moves only in beta cutoffs (#608)
- 🔍 Stop clearing quiet history (#637)
- 🔍 Take quiet history into consideration for LMR (#613)
- ⚡ Refactor move encoding methods and stop encoding special move flags individually (#622)
- ⚡ Store captured pieces as part of the move (#604)
- ⚡ Use jagged arrays (
[][]
) instead of multidimensional ones ([,]
) (#605, #606, #607) - ⚡ Simplify triple repetition detection logic, removing some branching (#623)
- ⚡ Make
Piece
an integer enum (#603)
Full Changelog: v1.2.0...v1.3.0
v1.2.0
- 🔍 Prune SEE bad captures in QSearch (#558)
- 🔍 Reduce SEE bad captures in regular search (#564, #571)
- 🔍 Use spsa tuned search values (#543, #553)
- ⚖️ Add rook mobility to eval (#539)
- 🧬 Improve move generation: hardcode castling moves and a few calculated variables (#541)
- 🧬 Implement SEE and order bad captures after killers but before quiet moves (#554)
- ⚡ Reduce TT entry size to 8 bytes (#544)
- ⚡ Optimize
go
command parsing (#545) - ⚡ Split
MoveGenerator.GenerateAllMoves
andMoveGenerator.GenerateCaptures
to avoid branching (#549) - ⚡ Set search thread as high priority (#546)
- ⚡ SEE micro-optimizations (#566)
- ⚡ Use stack-allocated span for movegen (#551, #596)
- ⚡ Optimize
PositionHash
(#582) - ⚡ Optimize castling and en-passant moves Zobrist hashing (#577)
- ⚡ Replace
EnPassantCaptureSquares
dictionary with equivalent array (#578) - ⚡ Optimize FEN parsing (#581)
- ⚡ Attempt to initialize
MoveGenerator
andGoCommand
asap (#576) - 🐛 Fix engine crash due to a negative calculated time to move (#555)
- 🐛 Add support for negative
wtime
andbtime
(#556) - 🐛 Enable
InvariantGlobalization
and fix crash in some Linux scenarios (#575)
Non strength-winning changes:
- Add option to run
bench
at different depths (#537)
Full Changelog: v1.1.0...v1.2.0
v1.1.0
- 🔍 Add Internal Iterative Reduction (IIR) (#507)
- 🔍 Add basic LMP (#512)
- 🔍 Improve TT replacement scheme: add required conditions for always replace (#526)
- 🔍 Add third killer move (#517, #525)
- 🔍 Use new history bonus formula based on Sirius and Berserk one (#527)
- 🔍 In case of 'fake' ponder-hit, research from depth 1 (#467)
- ⚖ Re-tune eval using some Stash data (#515)
- ⚡ Avoid PEXT array initialization when PEXT isn't supported (#516)
Full Changelog: v1.0.1...v1.1.0
v1.0.1
Non strength-winning changes:
-
🐛 Clamp static evaluation within +- checkmate limits, preventing illegal moves (and erratic behavior in general) in positions with too much material on one side (#510).
We're talking about positions here such as
QQQQQQQQ/QQQQQQQQ/QQQQQQQQ/QQQQQQQQ/QQQQQQQQ/QQQQQQQQ/QPPPPPPP/K6k b - - 0 1
, which will never happen in a real game but can be artificially set up (i.e. in lichess), so no strength change is expected in regular engine games.
Full Changelog: v1.0.0...v1.0.1