pgn2 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +34 -3
  3. data/.github/workflows/native.yml +32 -0
  4. data/.github/workflows/publish.yml +44 -2
  5. data/.github/workflows/release-gems.yml +40 -0
  6. data/.github/workflows/release.yml +52 -5
  7. data/.gitignore +9 -1
  8. data/.rubocop.yml +46 -6
  9. data/CHANGELOG.md +148 -0
  10. data/Gemfile +3 -0
  11. data/NOTICE.md +21 -0
  12. data/README.md +107 -5
  13. data/Rakefile +35 -10
  14. data/TODO.md +64 -0
  15. data/bench/baseline_moves.txt +38 -4
  16. data/bench/baseline_parse.txt +4 -4
  17. data/bench/cross_check.rb +61 -0
  18. data/bench/legal_moves.rb +38 -0
  19. data/bench/perft.rb +32 -0
  20. data/bench/profile_moves.rb +93 -0
  21. data/docs/superpowers/plans/2026-08-13-attack-masks-plan.md +51 -0
  22. data/docs/superpowers/plans/2026-08-13-perf-internals-plan.md +883 -0
  23. data/docs/superpowers/plans/2026-08-13-rust-bitboard-perft-plan.md +2442 -0
  24. data/docs/superpowers/plans/2026-08-14-chessie-migration.md +722 -0
  25. data/docs/superpowers/plans/2026-08-14-rust-integration-plan.md +444 -0
  26. data/docs/superpowers/specs/2026-08-13-attack-masks-design.md +57 -0
  27. data/docs/superpowers/specs/2026-08-13-perf-internals-design.md +111 -0
  28. data/docs/superpowers/specs/2026-08-13-rust-bitboard-perft-design.md +270 -0
  29. data/docs/superpowers/specs/2026-08-14-rust-integration-design.md +217 -0
  30. data/ext/pgn2_native/Cargo.lock +321 -0
  31. data/ext/pgn2_native/Cargo.toml +19 -0
  32. data/ext/pgn2_native/extconf.rb +8 -0
  33. data/ext/pgn2_native/pgn2-bitboard/Cargo.toml +10 -0
  34. data/ext/pgn2_native/pgn2-bitboard/src/board.rs +32 -0
  35. data/ext/pgn2_native/pgn2-bitboard/src/lib.rs +12 -0
  36. data/ext/pgn2_native/pgn2-bitboard/src/moves.rs +121 -0
  37. data/ext/pgn2_native/pgn2-bitboard/src/perft.rs +81 -0
  38. data/ext/pgn2_native/pgn2_native/Cargo.toml +11 -0
  39. data/ext/pgn2_native/pgn2_native/src/lib.rs +54 -0
  40. data/lib/pgn/bitboard.rb +13 -0
  41. data/lib/pgn/board.rb +64 -0
  42. data/lib/pgn/fen.rb +35 -46
  43. data/lib/pgn/game.rb +22 -13
  44. data/lib/pgn/move.rb +19 -15
  45. data/lib/pgn/move_calculator.rb +13 -1
  46. data/lib/pgn/notation.rb +24 -29
  47. data/lib/pgn/position.rb +60 -19
  48. data/lib/pgn/serializer.rb +13 -18
  49. data/lib/pgn/version.rb +1 -1
  50. data/lib/pgn/zobrist.rb +53 -0
  51. data/lib/pgn.rb +2 -0
  52. data/pgn2.gemspec +17 -10
  53. data/spec/bitboard_spec.rb +54 -0
  54. data/spec/board_spec.rb +53 -0
  55. data/spec/fen_spec.rb +65 -65
  56. data/spec/game_spec.rb +52 -15
  57. data/spec/lexer_spec.rb +5 -5
  58. data/spec/notation_spec.rb +5 -0
  59. data/spec/parser_spec.rb +8 -1
  60. data/spec/position_spec.rb +128 -27
  61. data/spec/serializer_spec.rb +4 -4
  62. data/spec/zobrist_spec.rb +46 -0
  63. metadata +97 -35
data/spec/lexer_spec.rb CHANGED
@@ -17,7 +17,7 @@ RSpec.describe PGN::Lexer do
17
17
 
18
18
  it 'discards % rest-of-line comments' do
19
19
  toks = lex("% a comment line\n[Event \"X\"]")
20
- expect(toks.map(&:type)).to eq([:lbracket, :tag_name, :string, :rbracket])
20
+ expect(toks.map(&:type)).to eq(%i[lbracket tag_name string rbracket])
21
21
  end
22
22
 
23
23
  it 'tokenizes the four single-character literals' do
@@ -57,8 +57,8 @@ RSpec.describe PGN::Lexer do
57
57
  end
58
58
 
59
59
  it 'tokenizes nested brace comments recursively' do
60
- toks = lex("{outer {inner} tail}")
61
- expect(toks[0].value).to eq("{outer {inner} tail}")
60
+ toks = lex('{outer {inner} tail}')
61
+ expect(toks[0].value).to eq('{outer {inner} tail}')
62
62
  end
63
63
 
64
64
  it 'tokenizes all game terminations' do
@@ -116,8 +116,8 @@ RSpec.describe PGN::Lexer do
116
116
  end
117
117
 
118
118
  it 'records byte offsets on every token' do
119
- toks = lex("1. e4 e5 1-0")
120
- expect(toks[0].offset).to eq(0) # "1."
119
+ toks = lex('1. e4 e5 1-0')
120
+ expect(toks[0].offset).to eq(0) # "1."
121
121
  expect(toks[1].offset).to eq(3) # "e4"
122
122
  expect(toks[2].offset).to eq(6) # "e5"
123
123
  expect(toks[3].offset).to eq(9) # "1-0"
@@ -22,6 +22,11 @@ describe PGN::Notation do
22
22
  'g1', 'f3')).to eq('Nf3')
23
23
  end
24
24
 
25
+ it 'generates a non-castling king walk' do
26
+ fen = '4k3/8/8/8/8/8/8/4K3 w - - 0 1'
27
+ expect(PGN::Notation.san(pos(fen), 'e1', 'e2')).to eq('Ke2')
28
+ end
29
+
25
30
  it 'generates a pawn capture with the origin file' do
26
31
  fen = 'rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2'
27
32
  expect(PGN::Notation.san(pos(fen), 'e4', 'd5')).to eq('exd5')
data/spec/parser_spec.rb CHANGED
@@ -107,7 +107,14 @@ describe PGN do
107
107
  it 'returns original game pgn' do
108
108
  games = PGN.parse(File.read('./spec/pgn_files/fen.pgn'))
109
109
  game = games.first
110
- expect(game.pgn).to eq "[Event \"Event 1\"]\n[FEN \"4brkn/4bp1p/3q2pP/8/2B3N1/1P4N1/2PP3P/1K2Q3 w - - 0 1\"]\n[PlyCount \"7\"]\n\n1. Qxe7 Qxe7 2. Ne4 Bc6 3. Nef6+ Qxf6 4. Nxf6# 1-0"
110
+ expected = <<~PGN.chomp
111
+ [Event "Event 1"]
112
+ [FEN "4brkn/4bp1p/3q2pP/8/2B3N1/1P4N1/2PP3P/1K2Q3 w - - 0 1"]
113
+ [PlyCount "7"]
114
+
115
+ 1. Qxe7 Qxe7 2. Ne4 Bc6 3. Nef6+ Qxf6 4. Nxf6# 1-0
116
+ PGN
117
+ expect(game.pgn).to eq expected
111
118
  end
112
119
 
113
120
  it 'returns original game pgn for second game' do
@@ -1,53 +1,50 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe PGN::Position do
4
-
5
- describe "start" do
6
-
7
- it "should have fullmove 1" do
4
+ describe 'start' do
5
+ it 'should have fullmove 1' do
8
6
  pos = PGN::Position.start
9
7
  pos.fullmove.should == 1
10
8
  end
11
-
12
9
  end
13
-
14
- context "disambiguating moves" do
15
- describe "using SAN square disambiguation" do
16
- pos = PGN::FEN.new("r1bqkb1r/pp1p1ppp/2n1pn2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 3 6").to_position
17
- next_pos = pos.move("Ndb5")
18
-
19
- it "should move the specified piece" do
20
- next_pos.board.at("d4").should be_nil
10
+
11
+ context 'disambiguating moves' do
12
+ describe 'using SAN square disambiguation' do
13
+ pos = PGN::FEN.new('r1bqkb1r/pp1p1ppp/2n1pn2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 3 6').to_position
14
+ next_pos = pos.move('Ndb5')
15
+
16
+ it 'should move the specified piece' do
17
+ next_pos.board.at('d4').should be_nil
21
18
  end
22
19
 
23
- it "should not move the other piece" do
24
- next_pos.board.at("c3").should == "N"
20
+ it 'should not move the other piece' do
21
+ next_pos.board.at('c3').should == 'N'
25
22
  end
26
23
  end
27
24
 
28
- describe "using discovered check" do
29
- pos = PGN::FEN.new("rnbqk2r/p1pp1ppp/1p2pn2/8/1bPP4/2N1P3/PP3PPP/R1BQKBNR w KQkq - 0 5").to_position
30
- next_pos = pos.move("Ne2")
25
+ describe 'using discovered check' do
26
+ pos = PGN::FEN.new('rnbqk2r/p1pp1ppp/1p2pn2/8/1bPP4/2N1P3/PP3PPP/R1BQKBNR w KQkq - 0 5').to_position
27
+ next_pos = pos.move('Ne2')
31
28
 
32
29
  it "should move the piece that doesn't give discovered check" do
33
- next_pos.board.at("g1").should be_nil
30
+ next_pos.board.at('g1').should be_nil
34
31
  end
35
32
 
36
33
  it "shouldn't move the other piece" do
37
- next_pos.board.at("c3").should == "N"
34
+ next_pos.board.at('c3').should == 'N'
38
35
  end
39
36
  end
40
37
 
41
- describe "with two pawns on the same file" do
42
- pos = PGN::FEN.new("r2q1rk1/4bppp/p3n3/1p2n3/4N3/1B2BP2/PP3P1P/R2Q1RK1 w - - 4 19").to_position
43
- next_pos = pos.move("f4")
38
+ describe 'with two pawns on the same file' do
39
+ pos = PGN::FEN.new('r2q1rk1/4bppp/p3n3/1p2n3/4N3/1B2BP2/PP3P1P/R2Q1RK1 w - - 4 19').to_position
40
+ next_pos = pos.move('f4')
44
41
 
45
- it "should move the pawn in front" do
46
- next_pos.board.at("f3").should be_nil
42
+ it 'should move the pawn in front' do
43
+ next_pos.board.at('f3').should be_nil
47
44
  end
48
45
 
49
- it "should not move the other pawn" do
50
- next_pos.board.at("f2").should == "P"
46
+ it 'should not move the other pawn' do
47
+ next_pos.board.at('f2').should == 'P'
51
48
  end
52
49
  end
53
50
  end
@@ -124,4 +121,108 @@ describe PGN::Position do
124
121
  expect(parsed.to_fen.to_s).to eq(fen)
125
122
  end
126
123
  end
124
+
125
+ describe '#zobrist and equality' do
126
+ it 'seeds the start position with a stable hash' do
127
+ expect(PGN::Position.start.zobrist).to be_an(Integer)
128
+ expect(PGN::Position.start.zobrist).to eq(PGN::Position.start.zobrist)
129
+ end
130
+
131
+ it 'updates the hash after a move (and matches a fresh seed)' do
132
+ start = PGN::Position.start
133
+ after = start.move('e4')
134
+ expect(after.zobrist).to eq(
135
+ PGN::Zobrist.seed(after.board, after.player, after.castling, after.en_passant)
136
+ )
137
+ end
138
+
139
+ it 'is equal to an independently built equivalent position' do
140
+ a = PGN::Position.start.move('e4').move('e5')
141
+ b = PGN::Position.start.move('e4').move('e5')
142
+ expect(a).to eq(b)
143
+ expect(a.hash).to eq(b.hash)
144
+ end
145
+
146
+ it 'is not equal when only the side to move differs' do
147
+ a = PGN::Position.start
148
+ b = PGN::Position.start.move('e4')
149
+ expect(a).not_to eq(b)
150
+ end
151
+
152
+ it 'is not equal when castling rights differ' do
153
+ fen = 'r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1'
154
+ a = PGN::FEN.new(fen).to_position
155
+ b = a.move('O-O')
156
+ expect(a).not_to eq(b)
157
+ end
158
+
159
+ it 'ignores halfmove and fullmove counters for equality' do
160
+ a = PGN::Position.new(PGN::Board.start, :white, %w[K Q k q], nil, 0, 1)
161
+ b = PGN::Position.new(PGN::Board.start, :white, %w[K Q k q], nil, 7, 9)
162
+ expect(a).to eq(b)
163
+ expect(a.hash).to eq(b.hash)
164
+ end
165
+
166
+ it 'keeps the replay hash in sync with a fresh seed for a whole game' do
167
+ moves = %w[e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Be2 e6]
168
+ pos = PGN::Position.start
169
+ moves.each do |m|
170
+ pos = pos.move(m)
171
+ expect(pos.zobrist).to eq(
172
+ PGN::Zobrist.seed(pos.board, pos.player, pos.castling, pos.en_passant)
173
+ ), "drift after #{m}"
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ RSpec.describe PGN::Position, '#perft' do
180
+ it 'returns 1 at depth 0 for the start position' do
181
+ expect(PGN::Position.start.perft(0)).to eq(1)
182
+ end
183
+
184
+ it 'matches published startpos perft values' do
185
+ p = PGN::Position.start
186
+ expect(p.perft(1)).to eq(20)
187
+ expect(p.perft(2)).to eq(400)
188
+ expect(p.perft(3)).to eq(8_902)
189
+ expect(p.perft(4)).to eq(197_281)
190
+ end
191
+
192
+ it 'matches published Kiwipete perft values' do
193
+ fen = 'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1'
194
+ p = PGN::FEN.new(fen).to_position
195
+ expect(p.perft(1)).to eq(48)
196
+ expect(p.perft(2)).to eq(2_039)
197
+ expect(p.perft(3)).to eq(97_862)
198
+ end
199
+
200
+ it 'raises ArgumentError on negative depth' do
201
+ expect { PGN::Position.start.perft(-1) }.to raise_error(ArgumentError)
202
+ end
203
+ end
204
+
205
+ RSpec.describe PGN::Position, '#legal_moves' do
206
+ it 'lists 20 legal moves from the start position' do
207
+ expect(PGN::Position.start.legal_moves.length).to eq(20)
208
+ end
209
+
210
+ it 'returns sorted UCI strings matching the UCI format' do
211
+ moves = PGN::Position.start.legal_moves
212
+ expect(moves).to eq(moves.sort)
213
+ expect(moves).to all(match(/\A[a-h][1-8][a-h][1-8][qrbn]?\z/))
214
+ end
215
+
216
+ it 'matches the engine direct output for the same FEN (delegation equivalence)' do
217
+ fen = 'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1'
218
+ pos = PGN::FEN.new(fen).to_position
219
+ expect(pos.legal_moves).to eq(PGN::Bitboard::Engine.new(fen).legal_moves)
220
+ end
221
+
222
+ it 'includes a promotion UCI when one is legal' do
223
+ # White king e1, black king h1, white pawn e7 — e7e8q must be legal.
224
+ fen = '8/4P3/8/8/8/8/8/4K2k w - - 0 1'
225
+ pos = PGN::FEN.new(fen).to_position
226
+ expect(pos.legal_moves).to include('e7e8q')
227
+ end
127
228
  end
@@ -62,10 +62,10 @@ describe PGN::Serializer do
62
62
  it 'reproduces the variations.pgn movetext shape, including 2... after variations' do
63
63
  game = PGN.parse(File.read('./spec/pgn_files/variations.pgn')).first
64
64
  expected = "[Black \"Petrov\"]\n[White \"Somebody\"]\n\n" \
65
- "1. e4 e5 2. Nf3 { comment } " \
66
- "(2. f4 exf4 { final variation }) " \
67
- "(2. Nc3 { other } 2... d5 (2... f5) 3. exd5) " \
68
- "2... Nf6 *\n"
65
+ '1. e4 e5 2. Nf3 { comment } ' \
66
+ '(2. f4 exf4 { final variation }) ' \
67
+ '(2. Nc3 { other } 2... d5 (2... f5) 3. exd5) ' \
68
+ "2... Nf6 *\n"
69
69
  expect(PGN::Serializer.new(game).to_s).to eq(expected)
70
70
  end
71
71
 
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe PGN::Zobrist do
6
+ it 'exposes table, side, castling, and ep_file constants' do
7
+ expect(PGN::Zobrist::TABLE).to be_a(Hash)
8
+ expect(PGN::Zobrist::TABLE.keys.sort).to eq(%w[B K N P Q R b k n p q r].sort)
9
+ expect(PGN::Zobrist::TABLE['P'].length).to eq(128)
10
+ expect(PGN::Zobrist::SIDE).to be_an(Integer)
11
+ expect(PGN::Zobrist::CASTLING).to be_a(Hash)
12
+ expect(PGN::Zobrist::CASTLING.keys.sort).to eq(%w[K Q k q].sort)
13
+ expect(PGN::Zobrist::EP_FILE.length).to eq(8)
14
+ end
15
+
16
+ it 'seeds the starting position to a stable integer' do
17
+ first = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
18
+ second = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
19
+ expect(first).to eq(second)
20
+ expect(first).to be_an(Integer)
21
+ end
22
+
23
+ it 'differs when the side to move changes' do
24
+ white_to_move = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
25
+ black_to_move = PGN::Zobrist.seed(PGN::Board.start, :black, %w[K Q k q], nil)
26
+ expect(white_to_move).not_to eq(black_to_move)
27
+ end
28
+
29
+ it 'differs when castling rights change' do
30
+ full = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
31
+ no_k = PGN::Zobrist.seed(PGN::Board.start, :white, %w[Q k q], nil)
32
+ expect(full).not_to eq(no_k)
33
+ end
34
+
35
+ it 'differs when an en-passant file is present vs absent' do
36
+ with_ep = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], 'e3')
37
+ no_ep = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
38
+ expect(with_ep).not_to eq(no_ep)
39
+ end
40
+
41
+ it 'is deterministic across processes (frozen constants)' do
42
+ expect(PGN::Zobrist::TABLE).to be_frozen
43
+ expect(PGN::Zobrist::CASTLING).to be_frozen
44
+ expect(PGN::Zobrist::EP_FILE).to be_frozen
45
+ end
46
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgn2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stacey Touset
@@ -9,8 +9,64 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-08-13 00:00:00.000000000 Z
12
+ date: 2026-08-14 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rb_sys
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.9.39
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.9.39
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake-compiler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.2'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake-compiler-dock
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.6'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.6'
56
+ - !ruby/object:Gem::Dependency
57
+ name: benchmark-ips
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
14
70
  - !ruby/object:Gem::Dependency
15
71
  name: bundler
16
72
  requirement: !ruby/object:Gem::Requirement
@@ -26,7 +82,7 @@ dependencies:
26
82
  - !ruby/object:Gem::Version
27
83
  version: '2.3'
28
84
  - !ruby/object:Gem::Dependency
29
- name: pry
85
+ name: bundler-audit
30
86
  requirement: !ruby/object:Gem::Requirement
31
87
  requirements:
32
88
  - - ">="
@@ -40,7 +96,7 @@ dependencies:
40
96
  - !ruby/object:Gem::Version
41
97
  version: '0'
42
98
  - !ruby/object:Gem::Dependency
43
- name: rake
99
+ name: memory_profiler
44
100
  requirement: !ruby/object:Gem::Requirement
45
101
  requirements:
46
102
  - - ">="
@@ -54,7 +110,7 @@ dependencies:
54
110
  - !ruby/object:Gem::Version
55
111
  version: '0'
56
112
  - !ruby/object:Gem::Dependency
57
- name: rspec
113
+ name: pry
58
114
  requirement: !ruby/object:Gem::Requirement
59
115
  requirements:
60
116
  - - ">="
@@ -82,7 +138,7 @@ dependencies:
82
138
  - !ruby/object:Gem::Version
83
139
  version: '0'
84
140
  - !ruby/object:Gem::Dependency
85
- name: benchmark-ips
141
+ name: rake
86
142
  requirement: !ruby/object:Gem::Requirement
87
143
  requirements:
88
144
  - - ">="
@@ -96,7 +152,7 @@ dependencies:
96
152
  - !ruby/object:Gem::Version
97
153
  version: '0'
98
154
  - !ruby/object:Gem::Dependency
99
- name: memory_profiler
155
+ name: rspec
100
156
  requirement: !ruby/object:Gem::Requirement
101
157
  requirements:
102
158
  - - ">="
@@ -128,11 +184,14 @@ email:
128
184
  - stacey@touset.org
129
185
  - muriloime@gmail.com
130
186
  executables: []
131
- extensions: []
187
+ extensions:
188
+ - ext/pgn2_native/extconf.rb
132
189
  extra_rdoc_files: []
133
190
  files:
134
191
  - ".github/workflows/ci.yml"
192
+ - ".github/workflows/native.yml"
135
193
  - ".github/workflows/publish.yml"
194
+ - ".github/workflows/release-gems.yml"
136
195
  - ".github/workflows/release.yml"
137
196
  - ".gitignore"
138
197
  - ".rspec"
@@ -140,6 +199,7 @@ files:
140
199
  - CHANGELOG.md
141
200
  - Gemfile
142
201
  - LICENSE.txt
202
+ - NOTICE.md
143
203
  - README.md
144
204
  - Rakefile
145
205
  - TODO.md
@@ -152,16 +212,39 @@ files:
152
212
  - bench/baseline_parse.pre-quickwins.txt
153
213
  - bench/baseline_parse.racc.txt
154
214
  - bench/baseline_parse.txt
215
+ - bench/cross_check.rb
216
+ - bench/legal_moves.rb
217
+ - bench/perft.rb
155
218
  - bench/profile_moves.rb
156
219
  - bench/profile_parse.rb
157
220
  - docs/superpowers/plans/2026-08-12-efficiency-optimizations.md
158
221
  - docs/superpowers/plans/2026-08-12-efficiency-tests-and-profiling.md
159
222
  - docs/superpowers/plans/2026-08-12-to-pgn-serialization.md
223
+ - docs/superpowers/plans/2026-08-13-attack-masks-plan.md
224
+ - docs/superpowers/plans/2026-08-13-perf-internals-plan.md
225
+ - docs/superpowers/plans/2026-08-13-rust-bitboard-perft-plan.md
160
226
  - docs/superpowers/plans/2026-08-13-whittle-to-racc-migration.md
227
+ - docs/superpowers/plans/2026-08-14-chessie-migration.md
228
+ - docs/superpowers/plans/2026-08-14-rust-integration-plan.md
161
229
  - docs/superpowers/specs/2026-08-12-to-pgn-serialization-design.md
230
+ - docs/superpowers/specs/2026-08-13-attack-masks-design.md
231
+ - docs/superpowers/specs/2026-08-13-perf-internals-design.md
162
232
  - docs/superpowers/specs/2026-08-13-pgn-performance-quick-wins-design.md
233
+ - docs/superpowers/specs/2026-08-13-rust-bitboard-perft-design.md
234
+ - docs/superpowers/specs/2026-08-14-rust-integration-design.md
163
235
  - examples/immortal_game.pgn
236
+ - ext/pgn2_native/Cargo.lock
237
+ - ext/pgn2_native/Cargo.toml
238
+ - ext/pgn2_native/extconf.rb
239
+ - ext/pgn2_native/pgn2-bitboard/Cargo.toml
240
+ - ext/pgn2_native/pgn2-bitboard/src/board.rs
241
+ - ext/pgn2_native/pgn2-bitboard/src/lib.rs
242
+ - ext/pgn2_native/pgn2-bitboard/src/moves.rs
243
+ - ext/pgn2_native/pgn2-bitboard/src/perft.rs
244
+ - ext/pgn2_native/pgn2_native/Cargo.toml
245
+ - ext/pgn2_native/pgn2_native/src/lib.rs
164
246
  - lib/pgn.rb
247
+ - lib/pgn/bitboard.rb
165
248
  - lib/pgn/board.rb
166
249
  - lib/pgn/fen.rb
167
250
  - lib/pgn/game.rb
@@ -175,7 +258,9 @@ files:
175
258
  - lib/pgn/position.rb
176
259
  - lib/pgn/serializer.rb
177
260
  - lib/pgn/version.rb
261
+ - lib/pgn/zobrist.rb
178
262
  - pgn2.gemspec
263
+ - spec/bitboard_spec.rb
179
264
  - spec/board_spec.rb
180
265
  - spec/fen_spec.rb
181
266
  - spec/game_spec.rb
@@ -202,14 +287,17 @@ files:
202
287
  - spec/position_spec.rb
203
288
  - spec/serializer_spec.rb
204
289
  - spec/spec_helper.rb
290
+ - spec/zobrist_spec.rb
205
291
  homepage: https://github.com/muriloime/pgn
206
292
  licenses:
207
293
  - MIT
294
+ - MPL-2.0
208
295
  metadata:
209
296
  homepage_uri: https://github.com/muriloime/pgn
210
297
  source_code_uri: https://github.com/muriloime/pgn
211
298
  bug_tracker_uri: https://github.com/muriloime/pgn/issues
212
299
  changelog_uri: https://github.com/muriloime/pgn/blob/main/CHANGELOG.md
300
+ rubygems_mfa_required: 'true'
213
301
  post_install_message:
214
302
  rdoc_options: []
215
303
  require_paths:
@@ -229,30 +317,4 @@ rubygems_version: 3.5.22
229
317
  signing_key:
230
318
  specification_version: 4
231
319
  summary: A PGN parser for Ruby
232
- test_files:
233
- - spec/board_spec.rb
234
- - spec/fen_spec.rb
235
- - spec/game_spec.rb
236
- - spec/lexer_spec.rb
237
- - spec/move_calculator_spec.rb
238
- - spec/move_spec.rb
239
- - spec/notation_spec.rb
240
- - spec/parser_explicit_spec.rb
241
- - spec/parser_spec.rb
242
- - spec/pgn_files/alternate_castling.pgn
243
- - spec/pgn_files/annotations.pgn
244
- - spec/pgn_files/comments.pgn
245
- - spec/pgn_files/doublequotes.pgn
246
- - spec/pgn_files/empty_variation_move.pgn
247
- - spec/pgn_files/fen.pgn
248
- - spec/pgn_files/multiline_comments.pgn
249
- - spec/pgn_files/nested_comments.pgn
250
- - spec/pgn_files/no_moves.pgn
251
- - spec/pgn_files/specialcharacters.pgn
252
- - spec/pgn_files/test.pgn
253
- - spec/pgn_files/two_annotations.pgn
254
- - spec/pgn_files/two_games.pgn
255
- - spec/pgn_files/variations.pgn
256
- - spec/position_spec.rb
257
- - spec/serializer_spec.rb
258
- - spec/spec_helper.rb
320
+ test_files: []