1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
//! Build script: generates the perfect-hash hand-evaluator tables into
//! `$OUT_DIR/eval_tables.rs`, which `src/core/eval.rs` then `include!`s.
//!
//! The algorithm is reimplemented from zekyll's OMPEval (MIT). This file is the
//! "compiler": it does all the expensive combinatorial work once, at build time,
//! and bakes the answers into flat arrays. At runtime, ranking a hand is then
//! just a couple of array loads and no branches worth mentioning.
//!
//!
//! # 1. What a "rank" is
//!
//! Every poker hand can be scored by a single 16-bit number. Higher is better.
//! We pack two fields into it:
//!
//! ```text
//! bit: 15 .. 12 | 11 ............ 0
//! category | subrank
//! (1..=9) | (0..=4095)
//! ```
//!
//! `category` is the hand class, ordered worst to best:
//!
//! ```text
//! 1 HIGH_CARD 4 TRIPS 7 FULL_HOUSE
//! 2 PAIR 5 STRAIGHT 8 QUADS
//! 3 TWO_PAIR 6 FLUSH 9 STRAIGHT_FLUSH
//! ```
//!
//! `subrank` breaks ties inside a category (which two pair, which kicker, etc.).
//! Because category sits in the high bits, comparing two scores as plain
//! integers gives the correct poker ordering for free.
//!
//! There are exactly 7462 distinct five-card hands once you ignore suits that do
//! not form a flush. Our generated tables collapse every possible hand onto one
//! of those 7462 scores; the build asserts that count as a self-check.
//!
//! Where does 7462 come from? There are C(52,5) = 2,598,960 distinct five-card
//! deals, but most are equivalent for ranking: clubs are interchangeable with
//! spades unless five of them make a flush, and the order you hold cards in
//! never matters. Counting the genuinely distinct VALUES, by category:
//!
//! ```text
//! category count how it is counted (13 ranks)
//! --------------- ----- ----------------------------------------------
//! Straight flush 10 one per high card, A-5 (wheel) up to 10-A
//! Four of a kind 156 13 quad ranks * 12 kicker ranks
//! Full house 156 13 trips ranks * 12 pair ranks
//! Flush 1,277 C(13,5) rank patterns - 10 straight patterns
//! Straight 10 same 10 high cards as straight flush
//! Three of a kind 858 13 trips ranks * C(12,2) kicker pairs
//! Two pair 858 C(13,2) pair ranks * 11 kicker ranks
//! One pair 2,860 13 pair ranks * C(12,3) kicker triples
//! High card 1,277 same C(13,5) - 10 patterns, but off-suit
//! --------------- -----
//! TOTAL 7,462
//! ```
//!
//! Flush and high card share the same 1,277 rank patterns: any five distinct
//! ranks that are not a straight. The only difference is whether they are
//! one-suited, and that is exactly the "suits matter only for a flush" rule. The
//! straight and straight-flush rows likewise share their 10 patterns. Sum the
//! column and you get 7,462; `assert_7462_classes` reconstructs this set from the
//! generated tables and checks the size.
//!
//!
//! # 2. The (key, mask) representation of a hand
//!
//! A hand is accumulated into two `u64` words as cards are dealt in. Both are
//! purely additive: dealing a card does `key += CARDS_KEY[c]` and
//! `mask |= CARDS_MASK[c]`, with no per-card branching. (See `eval.rs` for the
//! runtime side; this script generates `CARDS_KEY`, `CARDS_MASK`, and the
//! starting `DEFAULT_KEY`.)
//!
//! A card index is `suit * 13 + value`, so 0..=51:
//!
//! ```text
//! value: 0=2 1=3 2=4 ... 8=10 9=J 10=Q 11=K 12=A
//! suit : 0,1,2,3
//! index = suit*13 + value
//! ```
//!
//! ## 2a. mask: one bit per card
//!
//! `mask` is the obvious thing: bit `index` is set when that exact card is
//! present. Its only job is the flush path. The 13 bits for one suit can be
//! sliced out with `(mask >> (13 * suit)) & 0x1FFF` to get that suit's rank
//! pattern.
//!
//! ```text
//! mask bit layout (52 used bits):
//! [ suit3 13 bits | suit2 13 bits | suit1 13 bits | suit0 13 bits ]
//! 63.. ... ..0
//! ```
//!
//! ## 2b. key: rank fingerprint (low) + suit counters (high)
//!
//! `key` does double duty across two disjoint regions of the 64 bits:
//!
//! ```text
//! key bit layout:
//! 63 60 59 56 55 52 51 48 47 ........................ 0
//! [ suit3 ][ suit2 ][ suit1 ][ suit0 ][ additive rank key (32) ]
//! nibble nibble nibble nibble
//! ```
//!
//! Low 32 bits: the additive rank key. This ignores suit entirely and is a
//! single 32-bit fingerprint of the hand's rank histogram (the count of each
//! rank held). It is NOT a bit-packed array of counts; it is an arithmetic sum
//! that happens to be unique per histogram. Section 3 is entirely about this.
//!
//! High 16 bits: four 4-bit nibbles, one per suit, each a running count of how
//! many cards of that suit have been added. This is how we detect a flush
//! without a loop.
//!
//! ## 2c. The flush-bias trick
//!
//! Each suit nibble is pre-loaded with 3 (that is what `DEFAULT_KEY = 0x3333`
//! in the high nibbles means). Each card of a suit adds 1 to its nibble. So:
//!
//! ```text
//! cards of suit: 0 1 2 3 4 5 6 7
//! nibble value : 3 4 5 6 7 8 9 10
//! binary : 0011 0100 0101 0110 0111 1000 1001 1010
//! ^ the 0x8 bit flips at exactly 5
//! ```
//!
//! A flush needs 5+ cards of one suit, and 3 + 5 = 8 = `0b1000`. So the top bit
//! of a nibble (`0x8`) is set if and only if that suit has reached a flush.
//! Testing `key & (0x8888 << 48)` tells us in one AND whether any flush exists,
//! and which suit. The bias of 3 also guarantees the nibble never overflows: the
//! most cards of one suit in a 7-card hand is 7, giving 10, still inside 4 bits.
//!
//!
//! # 3. The additive rank key (low 32 bits)
//!
//! Forget suits for this whole section.
//!
//! ## 3a. The rank histogram (the concept)
//!
//! A hand's non-flush strength is fully determined by its rank histogram: how
//! many of each rank you hold. Write it as a 13-wide vector `counts[0..13]`,
//! where `counts[r]` is the number of cards of rank `r` (each 0..=4, total
//! 0..=7). Rank indices are `0=2, 1=3, ... 9=J, 10=Q, 11=K, 12=A`. For example,
//! the five cards K K A Q 7 give:
//!
//! ```text
//! rank : 2 3 4 5 6 7 8 9 T J Q K A
//! index: 0 1 2 3 4 5 6 7 8 9 10 11 12
//! count: 0 0 0 1 0 0 0 0 0 0 1 2 1
//! (7) (Q)(K)(A)
//! ```
//!
//! Suit is gone: K-of-clubs and K-of-spades both just bump `counts[11]`. Two
//! different deals with the same histogram are the same non-flush hand, so the
//! histogram is the thing we want to look up. The trouble is it is 13 numbers; we
//! want one small integer to index an array with.
//!
//! ## 3b. Collapsing the histogram to one number (the key)
//!
//! We do NOT store the 13 counts as bit-fields. Instead we give each rank a magic
//! 32-bit multiplier `RANKS[r]` and collapse the whole vector to a single sum:
//!
//! ```text
//! rank_key = sum over r of RANKS[r] * counts[r] (wrapping in u32)
//! ```
//!
//! This is why the layout is "additive": each card of rank `r` just adds the
//! constant `RANKS[r]` into the accumulator (that is the low 32 bits of
//! `CARDS_KEY`), so building the key needs no branches and no per-rank fields,
//! only `key += CARDS_KEY[card]` per card. The same K K A Q 7:
//!
//! ```text
//! 1 * RANKS[ 5] (the 7) = 0x00176005
//! 1 * RANKS[10] (the Q) = 0x0048c0e4
//! 2 * RANKS[11] (two Ks) = 0x0091e422
//! 1 * RANKS[12] (the A) = 0x00494493
//! ------------------------------------- wrapping sum
//! rank_key = 0x013b499e
//! ```
//!
//! The `rank_key` is a fingerprint of the histogram: you cannot read the
//! individual counts back out of it, but you do not need to. All that matters is
//! that the same histogram always yields the same number, and different
//! histograms yield different numbers.
//!
//! ## 3c. Why the magic constants work
//!
//! That second property is a strong claim: the `RANKS` constants (lifted from
//! OMPEval) are hand-picked so that every reachable histogram produces a DISTINCT
//! `rank_key`, with no collisions across the wrapping add. That is exactly what
//! makes the sum usable as a lookup key at all. The build does not take it on
//! faith: `assert_rank_keys_distinct` enumerates all reachable histograms and
//! checks that their keys are pairwise distinct. If a constant were ever wrong,
//! the build fails.
//!
//! These keys are sparse and large (spread across a 25-bit space, e.g. our
//! example sits at 0x013b499e), so we cannot index an array with them directly
//! without wasting gigabytes. Section 5 shows how we compress them.
//!
//!
//! # 4. From histogram to score: classify, then densify
//!
//! Two steps turn a histogram into a final 16-bit score.
//!
//! `classify` (section: the classifier) walks a fixed ladder of hand types and
//! returns `(category, payload)`. The `payload` is an ordering number: within a
//! category, a larger payload means a stronger hand, but the payloads are NOT
//! contiguous and their exact bit widths do not matter.
//!
//! `densify` then squashes each category's set of distinct payloads down to a
//! contiguous range of subranks 1, 2, 3, ... ordered by payload. Two histograms
//! that reach the same best five cards (e.g. the same flush makeable from
//! different six-card holdings) share a payload, hence share a subrank. After
//! densifying, `score = (category << 12) | subrank`.
//!
//!
//! # 5. Compressing the keys: a row-displacement perfect hash
//!
//! We have ~7000 live `rank_key` values scattered across a 25-bit space, and we
//! want an array small enough to fit in cache where `array[h(key)] == score`
//! with NO collisions and NO search at runtime. That is a "perfect hash".
//!
//! The construction used here is "first-fit row displacement". Picture the keys
//! laid out in a grid. Split each key into a high part (the "row") and a low part
//! (the "column"):
//!
//! ```text
//! row = key >> ROW_SHIFT (top bits; which row)
//! column = key & (ROW_WIDTH - 1) (low ROW_SHIFT bits; offset within row)
//! ```
//!
//! Now imagine a 2-D grid, one populated row per distinct `row` value, with the
//! keys of that row sitting at their `column` positions:
//!
//! ```text
//! column -> 0 1 2 ... ROW_WIDTH-1
//! row 0 [ . K . . K . ... ]
//! row 1 [ K . . K . . ... ]
//! row 2 [ . . K . . K ... ]
//! ```
//!
//! We want to flatten this into one 1-D array (`LOOKUP`) with no two `K`s
//! landing on the same cell. We do it by sliding each whole row left or right by
//! a per-row amount, the "displacement" or `offset`, until that row's keys drop
//! only into still-empty cells of the flat array:
//!
//! ```text
//! slot(key) = (key + ROW_OFFSETS[row]) wrapped into [0, LOOKUP_LEN)
//! ```
//!
//! Because every key in a row shares the same `row`, adding `offset` shifts the
//! whole row rigidly and preserves the spacing between its keys. We place the
//! densest rows first (they are the hardest to fit), and for each row we scan
//! `offset = 0, 1, 2, ...` taking the first one that does not collide -- "first
//! fit". `build_perfect_hash` does exactly this and then verifies that every key
//! reads its own score back out.
//!
//! At runtime, ranking a non-flush hand is therefore: take `rank_key`, look up
//! `ROW_OFFSETS[rank_key >> ROW_SHIFT]`, add it, and load `LOOKUP[that slot]`.
//!
//!
//! # 6. The flush path (separate, simpler)
//!
//! Flushes cannot use the rank-histogram key, because a flush cares which suit
//! and the histogram threw suits away. But a flush is simpler: it is decided by
//! the 13-bit rank pattern of the one flushed suit, which we already have in
//! `mask`. So we precompute a direct table `FLUSH_LOOKUP[pattern] = score` for
//! all 8192 patterns. `build_flush_table` fills it, scoring straight flushes
//! above ordinary flushes. No perfect hash needed; the pattern IS the index.
//!
//!
//! # 7. What this script emits
//!
//! Into `$OUT_DIR/eval_tables.rs`:
//! - sizing consts: LOOKUP_LEN, FLUSH_LEN, ROW_SHIFT, ROW_OFFSETS_LEN, DEFAULT_KEY
//! - LOOKUP : non-flush perfect-hash table (score per slot)
//! - FLUSH_LOOKUP : flush table (score per 13-bit suit pattern)
//! - ROW_OFFSETS : per-row displacements for the perfect hash
//! - CARDS_KEY : per-card key contribution (rank multiplier + suit nibble)
//! - CARDS_MASK : per-card mask bit
//!
//! The five self-check functions (`assert_*`) guard the invariants the runtime
//! depends on. Any violation fails the build rather than shipping a wrong table.
use HashMap;
use HashSet;
use env;
use Write as _;
use fs;
use Path;
// ============================================================================
// Constants
// ============================================================================
/// Per-rank key multipliers (OMPEval, MIT), indexed by value 0..=12.
///
/// These are the magic numbers from section 3: `rank_key` is the wrapping sum of
/// `RANKS[r] * counts[r]`, and they are chosen so distinct reachable histograms
/// never collide. `assert_rank_keys_distinct` proves that at build time.
const RANKS: = ;
/// Length of the non-flush perfect-hash table. A power of two so the runtime can
/// index it with a mask instead of a modulo. Large enough that first-fit row
/// displacement always finds room.
const LOOKUP_LEN: usize = 1 << 17;
/// Length of the flush table: one entry per 13-bit suit pattern.
const FLUSH_LEN: usize = 8192;
/// How many low bits of a `rank_key` form the "column"; the remaining high bits
/// form the "row". See the row-displacement diagram in section 5.
const ROW_SHIFT: u32 = 12;
/// Number of rows: `rank_key >> ROW_SHIFT` must land in `0..ROW_OFFSETS_LEN`.
const ROW_OFFSETS_LEN: usize = 8192;
// Hand categories, the high nibble of every score. See section 1.
const HIGH_CARD: u32 = 1;
const PAIR: u32 = 2;
const TWO_PAIR: u32 = 3;
const TRIPS: u32 = 4;
const STRAIGHT: u32 = 5;
const FLUSH: u32 = 6;
const FULL_HOUSE: u32 = 7;
const QUADS: u32 = 8;
const STRAIGHT_FLUSH: u32 = 9;
/// The non-flush categories, in the order their tables are densified.
const NONFLUSH_CATS: = ;
/// Rank-mask of the wheel straight A-2-3-4-5: aces low. Bit 12 (ace) plus bits
/// 0..=3 (five, four, three, two). Used by `straight`.
const WHEEL: u32 = 0b1_0000_0000_1111;
// ============================================================================
// Shared types
// ============================================================================
/// One classified hand source: `(source_key, category, payload)`.
///
/// `source_key` is a `rank_key` for non-flush entries or a 13-bit suit pattern
/// for flush entries; `densify` and the classifier only ever read `category` and
/// `payload`, so the same triple serves both paths.
type ClassEntry = ;
/// Map from a histogram's `rank_key` to its packed 16-bit score.
type ValueMap = ;
// ============================================================================
// Rank-mask primitives
//
// A "rank mask" is a 13-bit value: bit `r` set means rank `r` is present. These
// three helpers operate on rank masks and are the building blocks of both the
// classifier and the flush table.
// ============================================================================
/// Detect the best straight in a 13-bit rank mask.
///
/// Returns `Some(subrank)` where a larger subrank is a higher straight, or
/// `None` if there is no straight. The wheel (A-2-3-4-5) is the lowest straight
/// and maps to `Some(0)`.
///
/// The five-in-a-row test is branch-free. `v & (v<<1) & (v<<2) & (v<<3) & (v<<4)`
/// keeps a bit only where five consecutive ranks are all present:
///
/// ```text
/// v : ..0 0 1 1 1 1 1 0.. (ranks r..r+4 present)
/// v<<1 : ..0 1 1 1 1 1 0 0..
/// v<<2 : ..1 1 1 1 1 0 0 0..
/// v<<3..4 : (shifted further)
/// AND : ..0 0 0 0 1 0 0 0.. one surviving bit at the top of the run
/// ```
///
/// The surviving bit's position identifies the straight's high card. The wheel
/// is checked separately because its ace wraps from bit 12 down to bit 0.
/// Keep only the highest `n` set bits of a rank mask, clearing the rest.
///
/// Used to drop kickers that do not count, e.g. a high-card hand keeps its top 5
/// ranks. Repeatedly clears the lowest set bit (`v &= v - 1`) until `n` remain.
/// Keep only the single highest set bit of a rank mask (0 stays 0).
// ============================================================================
// The non-flush classifier
// ============================================================================
/// Classify a rank histogram into `(category, payload)`.
///
/// `counts[r]` is how many cards of rank `r` the hand holds. The return is the
/// hand `category` (section 1) and an ordering `payload`: within one category a
/// larger payload is a stronger hand. Payloads are not contiguous and their bit
/// widths are unimportant; `densify` compresses them to subranks afterwards.
///
/// This mirrors the runtime non-flush ladder in `rank_u64`. The ladder is walked
/// strongest structural feature first (quads, then full house, ...). The
/// `kept << 13 | kickers` shape simply stacks the defining ranks above the
/// kicker ranks so the payload sorts correctly.
// ============================================================================
// Histogram enumeration
// ============================================================================
/// The additive rank key of a histogram: the wrapping sum of
/// `RANKS[r] * counts[r]`. See section 3.
/// Visit every reachable rank histogram: each rank 0..=4, total cards 0..=7.
///
/// This is the full domain of the non-flush evaluator. A 7-card hold'em hand can
/// hold at most four of a rank and seven cards overall, so these bounds cover
/// every histogram the runtime can ever see.
/// Like `enumerate_counts`, but only visits histograms whose total is exactly
/// `remaining`. Used by the 7462-class self-check to enumerate five-card hands.
// ============================================================================
// Densification: payloads -> contiguous subranks
// ============================================================================
/// Map each distinct payload in `cat` to a contiguous subrank, ordered by
/// payload.
///
/// `entries` is `(rank_key, category, payload)` triples. The smallest payload in
/// the category becomes subrank 1, the next becomes 2, and so on. Equal payloads
/// (the same best-five reached different ways) collapse to one subrank. The
/// returned map is `payload -> subrank` for that one category.
// ============================================================================
// The non-flush value map: histogram key -> 16-bit score
// ============================================================================
/// Build `rank_key -> score` for every reachable non-flush histogram.
///
/// Enumerates all histograms, classifies each into `(category, payload)`, then
/// densifies payloads per category into subranks and packs `(category, subrank)`
/// into the final 16-bit score. Different histograms with the same `rank_key`
/// always classify to the same score (that is what the distinctness of keys
/// buys us), so inserting by key is unambiguous.
///
/// Also returns the raw `(rank_key, category, payload)` entries, which the
/// distinctness self-check reuses.
// ============================================================================
// The row-displacement perfect hash (section 5)
// ============================================================================
/// Slot a key lands in given its row's displacement, wrapped to `LOOKUP_LEN`.
///
/// This is the single source of the addressing arithmetic; `perf_hash` in
/// `eval.rs` mirrors it exactly. `LOOKUP_LEN` is a power of two, so the wrap is a
/// mask.
/// Place every live key into a collision-free `LOOKUP` table via first-fit row
/// displacement, returning `(LOOKUP, ROW_OFFSETS)`.
///
/// Steps, matching the diagram in section 5:
/// 1. Bucket keys by `row = key >> ROW_SHIFT`.
/// 2. Process rows densest first (hardest to place).
/// 3. For each row, scan `offset = 0, 1, 2, ...` and take the first that drops
/// all of the row's keys into still-empty slots; mark those slots used.
/// 4. Fill `LOOKUP[perf_slot(key, offset_of_its_row)] = score`.
///
/// Unused slots stay 0, which decodes as an impossible score, so a stray lookup
/// is detectably wrong rather than silently plausible.
// ============================================================================
// The flush table (section 6)
// ============================================================================
/// Build `FLUSH_LOOKUP`: a direct table from a 13-bit suit pattern to its score.
///
/// Only patterns with 5+ bits are flushes; the rest stay 0 and are never queried
/// (the runtime only consults this table once a flush is known to exist). A
/// pattern that is also five-in-a-row scores as a straight flush, otherwise as a
/// plain flush keeping its top five ranks. Both categories are densified just
/// like the non-flush ones.
// ============================================================================
// Per-card key and mask contributions (section 2)
// ============================================================================
/// Build the per-card contribution tables and the starting key.
///
/// Returns `(CARDS_KEY, CARDS_MASK, DEFAULT_KEY)`:
/// - `CARDS_KEY[i]` adds the card's rank multiplier into the low 32 bits and
/// +1 into its suit nibble at bit `48 + 4*suit`.
/// - `CARDS_MASK[i]` is just `1 << i`.
/// - `DEFAULT_KEY` pre-loads every suit nibble with 3 (`0x3333 << 48`), the
/// flush-bias from section 2c.
///
/// Indices run 0..=51 (`suit*13 + value`); the 64-wide arrays leave 52..=63 zero
/// so a masked-to-6-bit index is always in bounds.
// ============================================================================
// Self-checks: invariants the runtime depends on
//
// Each runs at build time and panics (failing the build) on violation, so a
// broken table can never ship.
// ============================================================================
/// Prove the `RANKS` multipliers are a perfect hash on histograms: every
/// reachable histogram has a distinct `rank_key`. See section 3.
/// Prove the perfect hash round-trips: every live key reads back its own score.
/// Prove the tables collapse onto exactly the 7462 distinct five-card hands.
///
/// Enumerates every five-card hand both ways -- non-flush histograms (total 5)
/// resolved through `value_of`, and flush patterns (exactly 5 bits) resolved
/// through `flush_lookup` -- collects the distinct scores, and checks the count.
/// 7462 is the textbook number of five-card equivalence classes.
// ============================================================================
// Code emission
// ============================================================================
/// Emit `pub(crate) static NAME: [u16; N] = [ ... ];`, 32 values per line.
/// Emit `pub(crate) static NAME: [u32; N] = [ ... ];`, 32 values per line.
/// Emit `pub(crate) static NAME: [u64; N] = [ ... ];`, 16 values per line.