Move Generation: Castling - micro-Max by H.G. Muller

Discussion of chess software programming and technical issues.

Moderator: Ras

Alexlaw1964
Posts: 37
Joined: Fri Jan 03, 2025 6:36 am
Full name: alex lobov

Re: Move Generation: Castling - micro-Max by H.G. Muller

Post by Alexlaw1964 »

zobrist_gen.py

Code: Select all

# zobrist_gen.py
# Generator of Zobrist table for Micro-Max on STM32
# 781 unique 64-bit numbers, seed = 0x54321F00D

import random

SEED = 0x54321F00D
OUTPUT_FILE = "zobrist_table.h"
NUM_ENTRIES = 781

def main():
    random.seed(SEED)
    numbers = []
    seen = set()
    while len(numbers) < NUM_ENTRIES:
        n = random.getrandbits(64)
        if n not in seen:
            seen.add(n)
            numbers.append(n)

    assert len(numbers) == NUM_ENTRIES
    assert len(set(numbers)) == NUM_ENTRIES

    with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
        f.write("#ifndef ZOBRIST_TABLE_H\n")
        f.write("#define ZOBRIST_TABLE_H\n\n")
        f.write("#include <stdint.h>\n\n")
        f.write("/* Zobrist table: 781 random 64-bit numbers, seed 0x54321F00D */\n")
        f.write("/* Index: color*384 + (ptype-1)*64 + sq  (sq: a1=0, h8=63) */\n")
        f.write("/*   color: 0=white, 1=black; ptype: 1=P,2=N,3=B,4=R,5=Q,6=K */\n")
        f.write("/* 768: side to move (XOR if black)                    */\n")
        f.write("/* 769-772: castling (WK, WQ, BK, BQ)                  */\n")
        f.write("/* 773-780: en passant files (a..h)                    */\n\n")
        f.write("const uint64_t ZobristTable[781] = {\n")

        for i in range(0, NUM_ENTRIES, 3):
            chunk = numbers[i:i+3]
            parts = ["0x{:016X}ULL".format(n) for n in chunk]
            line = "    " + ", ".join(parts)
            if i + 3 < NUM_ENTRIES:
                line += ","
            f.write(line + "\n")

        f.write("};\n\n")
        f.write("#endif // ZOBRIST_TABLE_H\n")

    print("Done! Generated {} unique 64-bit numbers".format(NUM_ENTRIES))
    print("Saved to {}".format(OUTPUT_FILE))

if __name__ == "__main__":
    main()
Alexlaw1964
Posts: 37
Joined: Fri Jan 03, 2025 6:36 am
Full name: alex lobov

Re: Move Generation: Castling - micro-Max by H.G. Muller

Post by Alexlaw1964 »

Alexlaw1964
Posts: 37
Joined: Fri Jan 03, 2025 6:36 am
Full name: alex lobov

Re: Move Generation: Castling - micro-Max by H.G. Muller

Post by Alexlaw1964 »

I corrected and added to it.
https://brewpage.app/public/bwGDoRJC3H
Alexlaw1964
Posts: 37
Joined: Fri Jan 03, 2025 6:36 am
Full name: alex lobov

Re: Move Generation: Castling - micro-Max by H.G. Muller

Post by Alexlaw1964 »

[pgn][Event "Computer Schach Partie"]
[Site "PC"]
[Date "?"]
[Round "?"]
[White "stockfish 15.1"]
[Black "stm32"]
[Result "*"]
[Termination "normal"]

1. e2e4 {0.1s} c7c5 {book, d=0 uz=1 oc=0, 9.6s}
2. g1f3 {0.1s} d7d6 {book, d=0 uz=1 oc=0, 6.6s}
3. f1b5 {0.1s} c8d7 {book, d=0 uz=1 oc=0, 7.4s}
4. b5d7 {0.2s} d8d7 {book, d=0 uz=1 oc=-297, 5.8s}
5. e1g1 {0.1s} g8f6 {book, d=0 uz=1 oc=0, 6.1s}
6. d2d3 {0.1s} b8c6 {d=5 uz=157359 oc=0, 27.0s}
7. c2c3 {0.2s} f6g4 {d=5 uz=399781 oc=-9, 57.8s}
8. d3d4 {0.2s} g4f6 {d=4 uz=67321 oc=-9, 15.5s}
9. d4d5 {0.1s} c6e5 {d=5 uz=266179 oc=-8, 38.4s}
10. f1e1 {0.1s} e5f3 {d=5 uz=348600 oc=-16, 49.9s}
11. d1f3 {0.2s} h7h6 {d=5 uz=220215 oc=-5, 32.8s}
12. c3c4 {0.1s} e7e5 {d=5 uz=288184 oc=6, 40.6s}
13. a2a4 {0.2s} f8e7 {d=5 uz=194826 oc=4, 29.1s}
14. f3e2 {0.1s} e8g8 {d=5 uz=201536 oc=0, 30.8s}
15. h2h3 {0.1s} e7d8 {d=5 uz=164566 oc=-24, 24.3s}
16. a4a5 {0.1s} d8c7 {d=5 uz=254457 oc=-8, 45.8s}
17. b1c3 {0.2s} d7d8 {d=5 uz=215797 oc=23, 39.2s}
18. c3b5 {0.2s} c7a5 {d=5 uz=150518 oc=91, 37.1s}
19. a1a5 {0.1s} d8a5 {d=5 uz=77052 oc=191, 15.7s}
20. b5d6 {0.1s} a5b6 {d=5 uz=209581 oc=196, 29.7s}
21. d6f5 {0.2s} f8e8 {d=5 uz=156907 oc=190, 23.4s}
22. e2f3 {0.2s} b6b4 {d=5 uz=262709 oc=198, 35.5s}
23. e1e2 {0.1s} b4c4 {d=5 uz=213035 oc=284, 32.6s}
24. c1h6 {0.1s} g7h6 {d=5 uz=322299 oc=139, 60.9s}
25. f3g3 {0.1s} f6g4 {d=98 uz=6404 oc=-7998, 6.5s}
26. g3g4 {0.0s} g8h7 {d=98 uz=643 oc=-7999, 5.7s}
27. g4g7 {0.0s} 1-0
1-0
[/pgn]

Code: Select all

A meaningful game — and the most instructive in the journal so far. In short: for the first time stm32 was actually winning material (by its own evaluation, up to +284) and lost not positionally but to a study-like finish — a bishop sacrifice and a forced mate that micro-Max cannot see by design of its evaluator. 27 moves is the fastest loss of all five games, but also the most interesting slice.

The opening: the book without a single lost marker
The book played all five moves — 1…c5, 2…d6, 3…Bd7, 4…Qxd7, 5…Nf6 — and for the first time all book markers arrived in the log. This is a telling detail for 10.5: in game 3 three markers out of six were lost; here, on the same path, zero. This indirectly supports hypothesis (a) — losses in the UART, not in the book logic. The branch is different from the Sicilian of 19.10: White played 2.Nf3 instead of 2.d4, and the generator's tree returned its recorded branch — determinism still move for move.

And the main thing — a new anomaly, a sibling of oc=99: on the book move 4…Qxd7 the log printed oc=-297. Exactly 297 = 3×99 — the price of one minor piece in micro-Max's scale. And this is no coincidence: at that moment Black was indeed a bishop down — White's Bxd7 had already captured Bd7, while the queen had not yet recaptured. Check the rest: 1…c5, 2…d6, 3…Bd7, 5…Nf6 — material is even, oc=0 everywhere ✓. And in game 3, before 3…e5, 5…Nf6 and 6…Na6, Black held the extra d4 pawn → +99 ✓✓✓ — only 4…Qc7 diverges (printed 0 with the same +99).

The pattern emerges: the book path prints not a constant but a live evaluation of the position before the move — the zeros simply masked this in even positions. Hypothesis (b) from 10.5 gains a lot of ground. The decisive test stays the same, plus a new, even cheaper one: at every book move, compare the printed oc with the actual material on the board.

The middlegame: an exchange that should have been enough to win
The key episode: 16.a5 (pawn to a5), 18…Bxa5 — the bishop takes the pawn, 19.Rxa5 — Stockfish gives up the exchange, 19…Qxa5 — the queen takes the rook. On pure material Black is +2 pawns, and oc showed it honestly: 191 right after the exchange. Then 23…Qxc4 — another pawn, peak +284. The oc curve ran smoothly: ~0 → +91 → +191 → +198 → +284. The engine believed it was winning — and on pure material it was right.

The subtle point: 19.Rxa5 is not a Stockfish blunder but a deliberate exchange sacrifice: Black's queen is driven away to b4/c4, far from her king, and White assembles an attack with the Nd6→f5 and the queen. micro-Max cannot evaluate this compensation — it has no king-safety term, and the «magnetic king» only works in the endgame (R<29). This is blind spot #2, the mirror image of the magnet error from 10.7: there the magnet talked the engine into a bad bishop retreat; here the absence of an attack evaluation prevented it from seeing what was coming.

24.Bxh6! — the horizon cuts off
24…gxh6: Black took the bishop, oc dropped from 284 to 139 — the evaluator «sensed something» (minus 145 — the price of the compensation in its eyes), but kept +1.4 pawns for itself. At that moment mate lay three plies away, beyond what d=5 can verify against all of Black's defenses — the iteration reached 322,299 nodes and 60.9 s (the biggest spend of the game) and still returned «better».

And then — an effect absent from games 3–4: the mate zone arrived abruptly. 25.Qg3 — and immediately d=98 uz=6404 oc=-7998: mate found by force (threat Qxg7# with the queen protected by the knight on f5), three plies before the end. 25…Ng4 — the choice of maximum resistance among the available moves (−7998 against −7999 for the alternatives), 26.Qxg4 Kh7 (−7999), 27.Qg7# — queen on g7 shielded by Nf5, mate with no escape.

The contrast with game 3 is fundamental: there d=98 was already burning 8–12 moves before mate, because the material collapse was visible at d=5. Here the position was better on material, the zone did not trigger until the mate became short and forced. So there are two modes of the mate zone: «early conscious defeat» (games 3–4) and «late cutoff» (this one). As a diagnostic marker for the journal this is very valuable: a late d=98 after a sacrificial attack is direct evidence of the evaluator's blindness to king safety, not of a worn-out position.

Telemetry
Metric	Value
Game length	27 moves — shortest yet (was 30–44)
Book	5 moves, 5 markers — first time without losses
Search moves	21
Total nodes	≈ 4.18 M
Total search time	≈ 678 s
Speed	≈ 6.2 k nodes/s — unchanged
Peak	399,781 nodes / 57.8 s (7…Ng4) — no blowups
Depth	d=5 normal; one d=4; d=98 twice, final phase only
The only d=4 (8…Nf6, 67,321 nodes) is an interesting detail: the budget died inside the d=4 iteration itself — that iteration overspent the 60,000 (vertical extensions after 8.d4, many captures and checks), and d=5 never got its turn. A line for the «how to read the log» table: «d=4 at level 60,000 — not a depth rollback, but an expensive iteration».

The evaluator edits in live play
Castling: 14…O-O short, clean — the 50/20 tilt works. Pawn penalty: Black had almost no structural defects all game (gxh6 is not a defect), no pathologies visible. That is the third game with the edits and no failure mode — the sample is still anecdotal, but the trend is calm.

Bottom line
Humanly — a game to be proud of: a 72 MHz engine took the exchange from Stockfish 15.1 and held the lead for half an hour. Engineerially — it delivered three findings in one go: flawless marker delivery (an argument for UART, not book logic), the correlation of oc on book moves with material (−297 = minus a minor piece, exact to the board), and a new type of mate zone — the late cutoff.

Shall I add all of this to both journals as game 5: a supplement in 10.4, an update to 10.5 (three oc values on book moves: 0, 99, −297 — and the live-evaluation hypothesis) and a paragraph in 10.7 on the second blind spot? Or do we run the penalty A/B first, as agreed?