How Strong a Shogi Engine
Can Codex Build for the Game Boy?

How much shogi AI can an AI coding agent fit into a game console from 1989?
And how strong is the resulting engine in actual play?

In this article, Codex means OpenAI's software engineering agent. It reads a repository, edits code, runs commands and tests, and records results under a given goal and set of constraints. The model used for this project was GPT-5.6 Sol. Codex was the agent that performed the development work, while GPT-5.6 Sol was the model responsible for its reasoning and code generation; this article distinguishes between the two.

The Game Boy shogi match screen
The match screen after the human's first move and the CPU's reply, captured at the native 160×144-pixel resolution.

The screen contains a 9×9 board and kanji pieces drawn as 16×16-pixel sprites. At startup, the player can choose to move first or second and give the CPU either 5 or 60 seconds per move. It is a human-versus-CPU shogi program in which pieces can move, capture, promote or decline promotion, and be dropped from hand.

The ROM is exactly 32,768 bytes, or 32KB. It does not use a memory bank controller (MBC) to switch external ROM or RAM banks. For ordinary moves, it checks nifu (two unpromoted pawns on one file), pieces with no legal destination, leaving one's king in check, and illegal pawn-drop mate.1

From the initial position, the completed engine searches 88 nodes with the 5-second setting and 1,061 nodes with the 60-second setting. What kind of opponent can a Game Boy shogi engine challenge when it thinks for a full minute?

The Assignment Given to Codex

At the start of development, the repository contained only a README, a license, and Git ignore settings. There was not a single line of shogi source code, and the README defined only these boundaries:

The implementation language, board representation, search method, and development tools were unspecified. Codex began with a C11 host engine, then expanded the work to SM83 code, tests, emulator control, and match runners. The first implementation commit added 17 files and about 1,700 lines; many small measurement and design commits followed.

Saying only that Codex wrote most of the code misses the division of responsibility. The human set the objective and acceptance criteria; Codex designed, implemented, and verified within that frame. Usability and playing strength in particular left questions that the program alone could not settle.

The human ran the ROM and reported concrete failures: pressing Down alone started a match, parts of the cursor remained on screen, and the display flashed white. Codex reproduced these reports in emulators, isolated their causes, and turned them into regression tests that included long key presses. For strength testing, the human chose meaningful opponents and stopped experiments when the comparison no longer answered the intended question.

This division is best understood not by counting lines of code, but by asking who framed the questions and who decided which results were acceptable.

Making Shogi Work on a PC

Before writing Game Boy code, Codex built a host engine from the same C sources. Investigating rule discrepancies only in a slow emulator would make every position expensive to debug. On a PC, unit tests run quickly and legal move sets can be compared with an external engine.

A position contains an 81-square array, seven types of pieces in hand for each side, the side to move, both king locations, the ply count, and repetition history. Board pieces use signed bytes: positive values belong to Black, negative values to White, and both share the same piece numbers. Only the pre-move differences are saved in Undo; search repeatedly makes and unmakes moves while reusing one position object.

A move fits in 16 bits. The source and destination use seven bits each, and one remaining bit marks promotion. Source values 81 through 87 represent drops of the seven piece types.

/* bits 0..6: destination, 7..13: source, 14: promotion */
typedef uint16_t Move;
#define MOVE_TO(m)       ((uint8_t)((m) & 127u))
#define MOVE_FROM(m)     ((uint8_t)(((m) >> 7) & 127u))
#define MOVE_PROMOTES(m) (((m) & 0x4000u) != 0)

The legal move generator follows the movement directions for each piece and emits pseudo-legal moves one at a time. Where promotion is optional, it emits promoted and unpromoted variants; pawns and lances entering the last rank, and knights entering the last two ranks, retain only mandatory promotion. Drops exclude dead-end squares and nifu.

Each candidate is then played, and any move that leaves the moving side's king in check is discarded. Only when a pawn drop checks the opposing king does the engine recursively test whether the opponent has no legal reply; if so, the drop is rejected as illegal pawn-drop mate. This order separates movement generation from rules that depend on the whole position.

Counting 719,731 Positions

Legal move generation uses perft as its regression baseline.2 The test recursively plays every legal move from the initial position and counts leaves at a specified depth. Depths 1 through 4 produce 30, 900, 25,470, and 719,731 positions.

perft 4 = 719731 became an invariant for later changes. Even when a search optimization changes scores or move order, about 720,000 positions verify that it has not changed the legal move set. Tests also confirm that make/unmake restores the board, hands, king locations, and position fingerprint.

The same positions were sent to Fairy-Stockfish 11.1. Initial, drop, check-evasion, optional-promotion, mandatory-promotion, and asymmetric positions produced identical legal move sets. Only a pawn-drop-mate position differed: that version's go perft included one illegal move. Rather than treating the external engine as infallible, the project checked its own unit test and shogi definition and recorded that move as a known difference.

The host engine also implements USI. Positions, move sequences, and search commands can therefore be sent over a standard protocol, enabling automated games between project builds and external engines. Candidates intended to speed up the Game Boy version can first be checked for legality and playing results on the host.

Ordinary Search Does Not Fit in 8KB of RAM

Running on a PC and running on a Game Boy in practical time were different problems. The Game Boy has only 8KB of work RAM. The first implementation allowed as many as 600 legal moves, so a 16-bit move array alone consumed 1,200 bytes. Placing such an array at every recursive depth was impossible.

The engine therefore stopped storing all legal moves and kept only a small generator state that yields one move at a time. Search scans the generator in phases: the transposition-table move, captures and promotions, then other moves. Finding a category can require several board scans, but no recursive level consumes a 1,200-byte move list.

The speed limit was lower than expected as well. With the same four-ply quiescence search as the PC engine, the first measurement visited only 13 nodes in 5 seconds and did not complete depth 1. An early fixed budget of 12,000 nodes still had not finished after 20 seconds. The fixed-node design was replaced with a hardware timer that stops search after 5 or 60 seconds.

Avoiding Scans

On the SM83, division and remainder by nine inside move generation made converting square numbers to files and ranks expensive. The engine placed 81-entry file and rank tables in ROM and replaced arithmetic with lookup. It also stopped testing every one of the 81 squares as a destination and instead follows the movement vectors for pawns, knights, rooks, and the other piece types. This alone reduced host perft depth 4 from about 0.28 to 0.14 seconds.

Check detection still scanned the entire board. The revised position caches both king squares in one byte each, traces eight rays outward from the king to the first piece, and checks only the two knight directions separately. In the initial position, where pieces surround the king, detection usually ends on adjacent squares. On the Game Boy, this change raised measured search from 45 to 66 nodes in 5 seconds and from 425 to 729 nodes in 60 seconds.

The 16-bit position fingerprint is also updated incrementally instead of rescanning 81 squares and every hand after each move. Only Zobrist values for the source, destination, changed hand pieces, and side to move are XORed. A simple linear fingerprint tried first collided often when pieces exchanged places and lost seven of eight games, so it was removed. The adopted version uses an approximately 6KB Zobrist table generated from a fixed seed, with tests against a full recomputation.

Giving 4KB to the Transposition Table

Half of work RAM goes to the transposition table. Each eight-byte entry contains a 16-bit position key, best move, score, depth, and bound type. There are 512 entries, totaling 4,096 bytes. The remaining RAM stores the position, search state, drawing buffers, and other state.

Overall ROM and RAM Allocation

Classifying the final link map by function shows that the shogi engine occupies about 62% of ROM. The transposition table alone uses half of RAM, and statically allocated memory totals 4,416 bytes.

ROM32,768 bytes
Engine Graphics Book UI
  • Shogi engine20,26561.8%
  • Screen tiles4,92815.0%
  • Opening book3,90111.9%
  • UI and main loop3,0349.3%
  • Header and startup4981.5%
  • Trailing free space1420.4%
RAM8,192 bytes
TT Stack and unallocated
  • Transposition table4,09650.0%
  • Runtime stack and unallocated3,77646.1%
  • Sprite transfer1602.0%
  • Board and hands1341.6%
  • UI and timer260.3%
Allocation of the completed ROM and work RAM. Values are bytes, totaled from the link map and object-area sizes.

The 20,265-byte engine includes position and rule code, evaluation and search, a 6,208-byte Zobrist table, and a 648-byte movement-direction table. The 3,901-byte opening book consists of 3,072 bytes of positions and candidate moves plus 829 bytes of lookup code. Of the 4,928 bytes of screen tiles, 4,096 bytes hold the board and pieces.

“Runtime stack and statically unallocated” does not mean 3,776 bytes remain free at all times. Recursive calls, move generators, and temporary undo data consume this area downward from the top while the engine runs.

From Material to Lightweight Positional Evaluation

The evaluation function returns an integer expressing how favorable a position is for the side to move. Its first term is material: pawn 100, lance 300, knight 320, silver 450, gold 520, bishop 650, and rook 750. Board and hand pieces have the same values, while promoted pieces use separate values.

The Game Boy build adds three inexpensive positional features: advancement of minor pieces, approximate mobility of major pieces, and friendly defenders adjacent to the king. Rather than count every attack or the detailed danger to the king, it uses features available during a single 81-square scan.

static const int piece_value[] = {
    0,100,300,320,450,520,650,750,20000,
    520,520,520,520,850,950
};

if (base == PAWN) v += advancement * SHOGI_PAWN_ADVANCE;
else if (base == LANCE) v += advancement * SHOGI_LANCE_ADVANCE;
else if (base == KNIGHT || base == SILVER)
    v += advancement * SHOGI_MINOR_ADVANCE;

Advancement measures ranks traveled from the piece owner's back rank. The 5-second setting adds 3 points per rank for a pawn, 1 for a lance, and 2 for a knight or silver. The 60-second setting uses 3, 3, and 1 respectively, valuing an advanced lance somewhat more. All are small relative to a 100-point pawn and cannot compensate for losing material.

Approximate mobility for rooks, bishops, horses, and dragons inspects only the adjacent square in each direction. An empty or enemy-occupied square counts as a usable direction; longer rays are not traced. For bishop-like pieces, a step toward the center scores more, distinguishing an opened bishop diagonal from an edge-pawn move in the initial position.

uint8_t to = ray_next[square][direction];
int8_t target = p->board[to];
if (!target || owner_of(target) != side) {
    if (type == BISHOP || type == HORSE) {
        int f = square_file[to];
        int edge_distance = f > 4 ? f - 4 : 4 - f;
        mobility += 5 - edge_distance;
    } else ++mobility;
}

King evaluation knows neither castle names nor desirable king squares. It counts adjacent friendly golds, silvers, promoted pawns, and related defenders, adding 4 points in the 5-second setting and 10 in the 60-second setting. It therefore cannot distinguish moving the king to safety from gathering golds and silvers around an uncastled king. Games where it prefers the latter directly reflect the implemented feature.

if (base == KING) for (int d = 0; d < 8; ++d) {
    uint8_t guard_square = ray_next[i][d];
    if (guard_square >= 81) continue;
    int8_t guard = p->board[guard_square];
    if (king_guard_owner[guard + 14] == side)
        v += SHOGI_LOW_KING_GUARD;
}

Separate evaluation functions are generated for the 5- and 60-second settings. Repeated branching inside the piece loop slowed the 5-second build, so ROM space was traded for duplicated code. The function is selected once from quiescence depth, with no per-piece branch. Adding positional terms was never assumed to make the engine “smarter than material”; each coefficient was compared in games.

Do Not Stop Exchanges at the Leaf

Calling evaluation the instant alpha-beta reaches its nominal depth can reward a position just after a capture, without seeing the recapture on the next move. When an arbitrary depth boundary determines whether a tactical exchange is visible, the result is called the horizon effect.

To reduce it, normal-search leaves enter quiescence search. Outside check, the current position is evaluated and then only captures and promotions are searched. In check, quiet blocks and king moves may be required, so all legal moves are generated.

if (!checked) {
    stand = search_evaluate(p, s);
    if (stand >= beta) return beta;
    if (stand > alpha) alpha = stand;
    if (!qdepth) return alpha;
}
while (checked ? next_legal_do(p, &g, &m, &u)
               : next_tactical_legal_do(p, &g, &m, &u)) {
    int score = -quiesce(p, -beta, -alpha,
                         qdepth - (qdepth != 0), s);
    /* update alpha and beta */
}

The 5-second setting normally searches one tactical ply and the 60-second setting two. Restoring the PC engine's four plies reproduced the initial failure of only 13 nodes in 5 seconds. Adding one quiescence ply reduced node throughput, yet a 32-game pilot scored 12 wins, 16 draws, and 4 losses at 5 seconds, and 23 wins, 7 draws, and 2 losses at 60 seconds against the preceding build.

The 60-second quiescence search also uses delta pruning. If the current score plus the maximum material gain of a capture or promotion cannot reach alpha, that move is skipped. Because captured shogi pieces enter the capturer's hand, the estimate counts both the removed board piece and the acquired unpromoted hand piece, plus any promotion gain. Checks are never skipped because their value cannot be bounded by material alone.

Search Promising Moves First

Alpha-beta discards more branches when good moves are searched first, because alpha and beta narrow sooner. This engine generates three phases: the transposition-table best move, captures and promotions, then quiet moves. It does not allocate an array to score and sort every move; instead it scans the incremental generator once per phase.

for (int phase = tt_move ? 0 : 1; phase < 3; ++phase) {
    MoveGen g; movegen_init(&g);
    while (phase == 1 ? next_tactical_legal_do(p, &g, &m, &u)
                      : next_legal_do(p, &g, &m, &u)) {
        /* phase 0: TT move, 1: captures/promotions, 2: quiet moves */
    }
}

The transposition table stores whether a score is exact, a lower bound, or an upper bound, as well as the move. When another line reaches the same position at sufficient stored depth, search can stop; even at insufficient depth, the stored move is tried first. With only 512 entries collisions are frequent, but the table supports both ordering and pruning within 8KB.

At the root, iterative deepening searches depths 1, 2, 3, and so on. This repeats work compared with beginning directly at depth 3. It nevertheless preserves the last completed result when time expires and places the previous best move first at the next depth.

Conditions for Searching Less

After captures and promotions, there is not enough time to search every quiet move to the same depth. The current ROM uses the following methods outside check and tactical cases.

Method Applied at Work omitted Re-search condition
futility pruning depth remaining 1 skip all unpromising quiet moves none
late move pruning 60-second setting, depth remaining ≤2 skip quiet moves from the 12th move onward none
late move reduction depth remaining ≥4 search quiet moves from the 7th move one ply shallower re-search at full depth if score exceeds alpha
principal variation search internal nodes, 60-second setting search moves after the first with a narrow window re-search with the normal window if score exceeds alpha

Futility pruning skips remaining quiet moves at depth 1 when static evaluation plus 100 points cannot reach alpha. The approximation says that a non-capturing, non-promoting move is unlikely to overcome a deficit larger than one pawn. It is not applied to check evasions, captures, promotions, or the transposition-table move.

Late move pruning omits moves that appear late in move order. At depth 2 or less in the 60-second setting, after eleven moves including the table move and tactical moves have been searched, the generator stops before remaining quiet moves. “Late” refers to search order within a position, not the game ply.

if (qdepth > 1 && phase == 2 && depth <= 2 &&
    !checked && move_count >= 11) {
    position_undo(p, m, &u);
    goto moves_done;
}

Late move reduction keeps later moves but searches them one ply shallower. It applies to non-checking quiet moves from the seventh onward when at least four plies remain. Any reduced move that exceeds alpha is searched again at full depth, leaving a path to recover a late good move.

Principal variation search (PVS) asks only whether later moves beat the current best, using a width-one window after the first move has raised alpha. The principal variation is the line following the current best move. A move that fails low is rejected cheaply; a move that exceeds alpha is re-searched with the normal window. PVS is enabled only at internal nodes in the 60-second setting, because re-search costs made it slower at the root in wall-clock measurements.

score = -negamax(p, child_depth, -alpha - 1, -alpha, s);
if (!s->stopped && score > alpha && score < beta)
    score = -negamax(p, child_depth, -beta, -alpha, s);

These are not all proof-preserving prunings. Futility and late move pruning can miss a good move that satisfies their conditions. PVS and late move reduction re-search when necessary, but re-search itself is costly on a slow CPU. Candidates were therefore evaluated not only on fixed tactical answers, but in games with Game Boy-equivalent time limits.

When Time Expires Mid-Iteration

If depth 2 has completed and 60 seconds expires partway through depth 3, a conventional engine can return the completed depth-2 result. This implementation may also use root candidates already evaluated at depth 3. Only a candidate sufficiently above the preceding best move is accepted; small score differences are treated as move-order noise.

if (!s.stopped) {
    result->best = best;
    result->completed_depth = depth;
} else if (best != principal &&
           best_score >= principal_score + partial_margin) {
    result->best = best;
}

The margin is 2 points at 5 seconds and 175 points at 60 seconds. Without partial depth-1 results, the 5-second setting would have almost no comparison among moves. At 60 seconds, quiescence and pruning create larger score fluctuations, so only a candidate more than a pawn ahead is accepted.

From depth 2 onward, an aspiration window sets the upper bound to the previous iteration's score plus 256 points. Only a fail-high is re-searched with the normal wide window. The lower bound remains wide from the start, making partial results easier to preserve at timeout.

In the completed build, the initial position reaches 88 nodes and completed depth 1 at 5 seconds, and 1,061 nodes and completed depth 3 at 60 seconds. The normal ROM is 32,768 bytes and static WRAM usage is 4,416 bytes. Only 142 bytes remain between the linked end and the end of ROM.

Faster Alone Is Not Enough

On an 8-bit CPU, a change that visits more nodes in the same time looks attractive. But node count measures speed, not move quality. Changing order or pruning can find important moves with fewer nodes, or race faster down bad branches.

An attempt to double the transposition table to 1,024 entries illustrates the distinction. Compressing entries from eight to seven bytes solved fixed mandatory-promotion and mate-in-one tests in fewer nodes. Yet at the current depth-3 budget it performed significantly worse than the 512-entry version on standard positions, Floodgate positions, and the 16-position suite. Because the tactical improvement did not transfer to overall play, the engine returned to 512 entries in 4,096 bytes.

A small machine-learned piece-square table failed similarly. On separate training and validation Floodgate games, the raw model raised winner-prediction accuracy from 52.02% to 61.77%. Quantizing it for ROM reduced accuracy to 53.90%, increased the nodes needed for fixed tactics, and slowed the Game Boy. Both time settings also lost their 32-game pilots, so predictive accuracy was not treated as playing-strength improvement.

Root PVS won narrowly when both versions received the same 734 nodes. On the Game Boy, however, the fixed cost of re-search after narrow-window failures did not appear in that node count and reduced the work completed in 60 seconds by about 7.5%. The wall-clock comparison scored 158 wins, 113 draws, and 241 losses, while the ROM grew by 263 bytes, so root PVS was rejected.

Tests of roughly 32 games were used only to eliminate clearly bad candidates. When a small difference remained, 512 distinct starting positions extracted from Floodgate games were color-swapped for 1,024 games. Reports included a Wilson 95% confidence interval and a two-sided binomial-test value, and each color-swapped pair shared the same starting position and random seed.

Statistics still did not decide the design automatically. Selecting only the best among many candidates can overfit the same position set. Some evaluation coefficients were rejected when gains failed to reproduce on a separate set of 512 positions never used for selection. Distinguishing what speed, fixed tactics, matches, and holdout positions measured became a condition for spending the next bytes of ROM.

Repairing the Opening with a 3KB Book

Even after search was simplified, five seconds barely compared all initial moves at depth 1. The evaluation function knew little about coherent piece development, so it often left the king unmoved and merely brought golds and silvers closer. The first few moves were therefore supplemented with an opening book before search began.

The source was the public 2026 CSA game archive from the Floodgate online computer-shogi server. From 72,127 games, the project selected 15,279 in which both players were rated at least 3,000, at least 40 plies were played, and the game ended by resignation or entering-king declaration. It aggregated the first 24 plies, 366,696 moves total, weighting a winner's move by 3 and a loser's by 1.

The final build stores 312 positions and 512 candidates. Each six-byte candidate contains a 16-bit position key, 16-bit move, weight, and ply, so the data occupies 3,072 bytes. Each position keeps at most two choices and samples them by weight instead of fixing one move.

Randomness uses an 8-bit xorshift. It requires neither multiplication nor large state, and real hardware can seed it from the timer and scanline position. Providing the same seed reproduces the same branch, so color-swapped test pairs use identical seeds.

A 16-bit position key can collide across different positions. A book move is therefore never played unchecked. The current legal moves are generated first, the selected move must exist, and another candidate with the same key is tested after a collision.

The book was not intended to lock the engine into one opening. After the first move, branches open the bishop diagonal, advance the rook pawn, push central or edge pawns, and develop the king or golds. The final first move is nevertheless strongly biased: P-7f has probability 255/256 and P-2f probability 1/256. Shallow search handled positions after leaving a double-wing-attack line poorly, while biasing toward P-7f was more robust in pilot games.

This bias had a cost. After the same book move, both opponent and Game Boy often followed the same deterministic continuation. Repeated identical records in the final matches could no longer be counted simply as “twenty trials.”

Running in an Emulator Is Not Completion

Automated rule and search tests did not catch every defect seen by a person holding a key. The clearest example was the Windows BGB emulator starting a match when Down alone was pressed immediately after startup.

Codex first scanned 724 combinations of press start time and hold length in headless PyBoy. None started a match. The mGBA debugger likewise read Down as Down, without START.

Failure to reproduce did not prove the input code correct. Using the BGB configuration and exact input conditions supplied by the human, Codex ran BGB 1.6.6 under Wine. Holding Down for 0.5 seconds advanced the pre-fix ROM to the board.

The cause was the Game Boy JOYP input register. Eight keys share four bits arranged in two rows; bit 3 means Down in the direction row and START in the button row. The old code read once immediately after switching rows, while the prior row's Down value still remained and was interpreted as START.

Discarding one read after each switch fixed Down but left Right occasionally turning into A and making an unintended move. The final code discards four reads after selecting a row and accepts only the fifth. Long-press tests cover every direction/button pair sharing a bit, not only Down and START.

Automated tests were adjusted to human hold times. Alongside one-frame taps, they verify that 10-, 30-, and 120-frame holds produce exactly one action. A separate defect mistook a transient zero during release for a completed release and accepted one physical press twice; the engine now waits for two consecutive VBlanks with no input before accepting another action.

The one-frame white flash occurred because the LCD was stopped for every cursor move. Coordinate calculation and tile selection were moved into a work-RAM staging step, leaving only a few predetermined VRAM writes during VBlank. Recorded video was checked for a single white or half-updated frame, not merely for a correct final screenshot.

A Large Bug in Tiny Startup Code

Another display corruption lived in custom startup assembly rather than C. Before entering Game Boy C code, the startup routine copies initialized data and clears zero-initialized storage. An OR used to test the remaining clear length left a nonzero value in register A, which the loop then wrote repeatedly into memory.

The defect changed the queued drawing-update count to 19, generated invalid VRAM addresses, and even wrote an unintended interrupt-enable value. Adding xor a before the loop recreated zero, and the final startup explicitly initializes static storage, interrupts, and sprite transfer.

The human saw only a key producing the wrong action or a few pixels left behind. Codex transformed those observations into reproducible configurations, held-input sequences, register checks, recordings, and regression tests. Without that exchange, a ROM that worked only in PyBoy and mGBA might have been declared complete.

Making the Game Boy ROM Play Matches

Strength could not be measured by substituting the lightweight PC build. Even with identical C sources, SM83 move-generation time, timer interrupts, and display load change which move survives a timeout.

A dedicated evaluation ROM therefore ran under mGBA. It exposes a mailbox in work RAM through which external code writes positions and commands using the mGBA debugger. The PC side presents this control path as a USI engine and connects it to a normal match runner.

match runner
    ↓ USI
mGBA debugger
    ↓ WRAM mailbox
evaluation Game Boy ROM
    ↓ bestmove
cshogi legality and termination checks

The normal and evaluation ROMs link the search core from the same object file. Every move returned by the ROM is applied to a cshogi position, checking legality, side to move, mate, and termination reason. This makes repeated games use the actual instruction speed of the Game Boy search.

Merely creating an evaluation ROM did not reproduce normal timing. The first version stopped the LCD and searched 90 nodes in five seconds, while the normal ROM searched 88. VBlank interrupts and sprite DMA consume CPU time in the displayed game.

Running the LCD and the same VBlank/DMA load in the evaluation ROM aligned the initial-position results at 88 nodes for 5 seconds and 1,061 for 60. Records produced before that correction were excluded. Even a two-node advantage from hiding the screen matters when the entire five-second search visits only 88 nodes.

Playing YaneuraOu at Its Minimum Setting

Opponents included random, which chooses uniformly from legal moves; MaterialLv1, the pure-material evaluation build of YaneuraOu; and Suisho 5. The original plan was to adjust each opponent's node budget until it played evenly with the Game Boy.

The gap remained large even at the smallest command accepted by YaneuraOu and Suisho 5, go nodes 1. In a 32-game color-swapped pilot over 16 positions, the 5-second build scored 1 win, 1 draw, and 30 losses against Suisho 5, and 1 win and 31 losses against MaterialLv1. The 60-second build scored 1 win and 31 losses against Suisho 5, and 12 wins, 1 draw, and 19 losses against MaterialLv1. Every condition was below 50%.

The equal-strength node budget lay below the positive integer range available to the opponents. Further Suisho 5 games, holdout validation, and precise estimation of an equal point stopped there.

Instead, twenty games from the standard initial position were saved for each condition. They are illustrative records, not estimates of a general win rate.

Time Opponent GB wins Draws GB losses
5 sec MaterialLv1 0 0 20
5 sec random 17 0 3
60 sec MaterialLv1 3 0 17
60 sec random 20 0 0

All twenty random games at each time setting had distinct move sequences. The MaterialLv1 games at each setting had effectively only three sequences, repeated 10, 7, and 3 times after both deterministic engines followed the same continuation from a book branch.

The three wins at 60 seconds must not be read as three successes in twenty independent trials. All three replayed the same record: one winning line repeated three times. The table describes saved files, but the sample cannot support a population win rate with a confidence interval.

The Misleading Phrase “It Lost to Material Alone”

The name MaterialLv1 may suggest a simple one-ply opponent. But Lv1 describes only the static evaluation function, not the whole searcher. “Material” means it returns the difference in piece values without positional, king-safety, or attack-map terms.

The searcher using that score is normal YaneuraOu. It enumerates every root move, uses iterative-deepening alpha-beta, and recursively searches captures, promotions, checks, and evasions in quiescence. Mate-in-one detection, a transposition table, SEE filtering of losing exchanges, and capture ordering remain enabled. SEE (static exchange evaluation) cheaply estimates the material outcome of repeated captures on one square.

Furthermore, go nodes 1 did not mean “make one move and stop immediately.” YaneuraOu v7.6.3 enters quiescence before checking the node limit at depth zero, and does not check that limit inside quiescence. Its first depth-1 iteration therefore completes every root move and each move's quiescence before stopping.

In saved standard-position games, MaterialLv1 reported an average of 86.0 nodes per move against the 5-second build and 104.2 against the 60-second build. Despite a requested budget of one, it processed as many as 319 nodes and completed depth 1 on every move.

The Game Boy checks timeout at every normal and quiescence node. Among 715 non-book moves at 5 seconds, 359 stopped at depth 0 without comparing all legal root moves at one depth. Similar totals near 90 nodes therefore represented different amounts of completed work.

The Game Boy evaluation has more terms than pure material: advancement, approximate rook/bishop mobility, and nearby king defenders. More terms do not guarantee greater accuracy. Piece-value ratios, coarse features, incomplete search, and short quiescence can combine to select worse moves than a pure-material opponent.

This was not a comparison in which “the positional Game Boy lost to one-ply material.” It compared a modern searcher that completes every candidate and tactical exchange even at its minimum setting with a search aggressively shortened for an 8-bit CPU and interrupted by time. Comparing only the evaluation functions would require a separate experiment that swaps them into the same searcher.

Strengths and Weaknesses Visible in the Games

The saved records cannot estimate win rates, but they do show which moves the engine chose. The following examples take one game each against random and MaterialLv1.

The diagrams were produced by replaying the saved records in ShogiHome.

5 Seconds vs. random (Game Boy as Black)

At five seconds, the engine leaves its king on the starting square and brings golds and silvers nearby. This is not natural development after leaving the book; it exposes the coarseness of measuring king safety only by adjacent defenders.

Opening position from the 5-second Game Boy versus random, with the Game Boy as Black
Opening play with the Game Boy as Black at five seconds. Golds and silvers approach while the king remains unmoved.

In the endgame it nevertheless brought a rook, dragon, horse, and gold around the opposing king and delivered mate. Random has no positional objective and chooses checks and material gains with the same probability as any legal move. The Game Boy's castle is strange, but material, advancement, and search for checks still give its play direction.

Final position from the 5-second Game Boy versus random, with the Game Boy as Black
Final position with the Game Boy as Black at five seconds. Attacking pieces surround the opposing king and deliver mate.

One game cannot establish a win percentage against random. Still, the 40 saved games with distinct sequences produced 37 wins, showing play more purposeful than uniform legal-move selection.

60 Seconds vs. MaterialLv1 (Game Boy as White)

At sixty seconds, the engine still tends to leave the king in place and gather golds and silvers. Deeper search does not change the king formation preferred by the evaluation function itself.

Early-middle position from the 60-second Game Boy versus MaterialLv1, with the Game Boy as White
Early-middle play with the Game Boy as White at sixty seconds. Golds and silvers gather around the uncastled king.

In the illustrated win, the Game Boy gave the opponent many pieces but eventually mated with a dragon. MaterialLv1's static evaluation does not count king safety, but its normal searcher's mate-in-one logic remains active. This game is an example of a legal mate, not a win caused by an accidental illegal move.

Final position from the 60-second Game Boy versus MaterialLv1, with the Game Boy as White
Final position with the Game Boy as White at sixty seconds. Despite giving away material, it mates the opposing king.

All three 60-second wins used this same move sequence. The engine did not discover three different ways to win; it replayed one line from the same book branch.

How Old Were the Techniques Put into the 1989 Hardware?

The Game Boy launched in Japan on April 21, 1989.3 Calling the project a port of modern shogi AI to an old console may suggest that its search methods were invented after the hardware. Publication dates tell a different story: the skeleton of this ROM's search predates the Game Boy.

The Search Skeleton Is Older Than the Console

The idea behind quiescence search predates the Game Boy by 39 years. In 1950, Claude Shannon wrote that applying evaluation in the middle of exchanges was of little value and proposed following forcing changes until a relatively stable position.4 That is the same reason this engine extends leaves through captures and promotions.

Year Technique or source Role in this implementation
1950 Search to a quiescent position Quiescence through captures and promotions
1970 Zobrist board hashing 16-bit fingerprints and transposition table
1975 Analysis of alpha-beta Main search skeleton
1980 Scout Lineage of narrow-window tests for later moves
1983 NegaScout Lineage of re-searching only moves that exceed the bound
1986 Comparison of PVS and aspiration Internal 60-second search and search windows
Apr. 21, 1989 Game Boy launch Reference date

Zobrist's 1970 technical report described hashing boards for chess, checkers, and Go.5 Knuth and Moore analyzed the correctness and complexity of alpha-beta in 1975.6

PVS's narrow windows also have a pre-launch lineage. Judea Pearl published Scout in 1980, and Alexander Reinefeld published its improvement NegaScout in 1983.7 A 1986 review compared PVS and aspiration as existing game-tree search methods.8

Late move reduction could not be assigned one invention year. A paper on the SEX algorithm, which varies work by move, appeared in March 1989, roughly seven weeks before the Game Boy launch. A public shogi evaluation combining alpha-beta with futility pruning, null-move pruning, and late move reduction appeared in 2012.9 That paper reported reducing the effective branching factor to 2.8 in shogi endgames whose raw average was about 80.

No primary source establishing the first appearance of the ROM's late move pruning or delta pruning was identified. Methods with uncertain origins are not classified here as post-Game-Boy inventions.

Development Infrastructure That Arrived Later

What appeared after the launch was less the basic search inside ROM than the infrastructure for repeated implementation, validation, and games. The dates below come from official specifications, project histories, proposals, and operating records.

Year Technology or infrastructure Role in this project
1999 Public SDCC development Compile C into an SM83 ROM
2005 Git Preserve small working changes and rejected experiments
2007 USI draft Connect engines and match runners through text
2008 Continuous Floodgate games Supply records for the book and validation positions
2011 C11 Language standard for host and Game Boy builds
2016 Game Boy support in mGBA Debugging, automation, and wall-clock measurement
2025 Codex Iterate implementation, tests, measurements, and records

SDCC moved its public development to SourceForge in December 1999 and now lists SM83 among its targets.10 Git began in 2005 and preserved working milestones and experimental differences in this repository.11

The first USI draft, dated January 24, 2007, explicitly separated a shogi GUI from an engine through standard-input and standard-output text.12 This project used that separation to connect the host engine to match runners. Floodgate's continuous-game mode began operation on February 9, 2008; its public records supplied both opening-book games and diverse starting positions.13

The C language predates the Game Boy, but the C11 edition used here was published in December 2011.14 mGBA began in 2013 and added Game Boy support in 2016.15 Automated measurement of hardware-equivalent nodes and inspection of search results in RAM depend on that emulator's debugger.

Codex's cloud software engineering agent was released as a research preview on May 16, 2025.16 It did not invent alpha-beta or quiescence search. Its role was to adapt established methods to 32KB of ROM and 8KB of RAM, run tests and matches, and preserve even rejection reasons in the repository.

The machine-code core of this ROM could therefore plausibly have been designed with knowledge available in 1989. The same development process could not have been reproduced then. Standard protocols, public game archives, emulators with debuggers, distributed version history, and coding agents all arrived later.

What Codex Could Do and What the Human Decided

Codex built not only the shogi engine and ROM, but the machinery for testing correctness and strength. It checked legal generation with perft and an external engine, controlled emulators through debuggers, and transformed Floodgate records into a book and starting positions. It also left an evaluation ROM, USI bridge, parallel match runner, statistical summaries, reproduction steps, and records of failed candidates.

Large candidate comparisons were especially suitable for a coding agent. It could change one constant or implementation, then run rule tests, perft, fixed tactics, hardware-node measurements, and matches in sequence, reverting poor candidates. Because rejected ideas were documented, a later experiment under different conditions could compare against their original assumptions.

The human, however, first noticed what felt wrong during actual use. Neither the mistaken Down input nor cursor trails appeared in final-screen comparisons alone. After the human supplied screenshots and exact operations, Codex converted them into reproducible tests.

The human also decided what a strength comparison meant. They selected opponents that would make the result understandable and stopped precise measurement once even the minimum setting was far stronger. Adding 108 games completed during selection would have increased the number, but keeping a selection-stage sample out of the formal result was another human decision.

“How strong is it?” is not answered automatically by adding games. The analysis had to avoid counting dependent records, inspect what the opponent's nodes 1 actually executed, and limit the conclusion to what the measurement supported.

Impressive for a Game Boy, Far Too Weak as a Shogi Engine

The completed ROM fits in 32KB and plays shogi on a Game Boy with promotion, drops, nifu, self-check, and illegal pawn-drop mate. It searches 88 nodes in five seconds and 1,061 nodes in sixty, and won 37 of the 40 saved random games.

Even after thinking for sixty seconds, however, it remained far below MaterialLv1, a modern normal searcher using pure material evaluation. It won only 3 of 40 saved games, and those three were replays of the same record. Because it was weaker than the minimum-node settings of both Suisho 5 and MaterialLv1, the original plan to estimate an equal-strength point could not be carried out.

Rather than ending the diagnosis at “because it is a Game Boy,” the project measured the missing search coverage. At five seconds, roughly 90 nodes still fail to complete depth 1 on half of non-book moves. MaterialLv1, by contrast, completes every root move and recursive quiescence before stopping even when asked for one node. That difference in stopping behavior and completed search could not be closed by adding a few evaluation terms.

Codex autonomously handled design, implementation, tests, emulator automation, and large match runs. The human still had to notice display and input anomalies, question what comparisons meant, and decide when to stop an experiment.

It worked hard by Game Boy standards, but it is far too weak as a shogi engine. Even so, the project measured and explained not only how much could be built, but where the same ROM stopped being competitive.


  1. Repetition is simplified to save memory. The engine stores 16-bit fingerprints for the latest 16 positions and declares a draw when the same fingerprint appears four times. It does not implement loss by perpetual check and cannot fully cover cycles longer than 16 positions or fingerprint collisions.
  2. Short for “performance test.” Because it recursively performs only legal move generation, make, and unmake without evaluation, shogi engines can use it to test rule implementations.
  3. Nintendo's news release gives April 21, 1989 as the Game Boy's Japanese release date.
  4. Claude E. Shannon, “Programming a Computer for Playing Chess”, 1950.
  5. Albert L. Zobrist, “A New Hashing Method With Application for Game Playing”, University of Wisconsin Technical Report 88, 1970.
  6. Donald E. Knuth and Ronald W. Moore, “An Analysis of Alpha-Beta Pruning”, 1975.
  7. Judea Pearl, “SCOUT: A Simple Game-Searching Algorithm with Proven Optimal Properties”, 1980. Alexander Reinefeld, “An Improvement to the Scout Tree Search Algorithm”, 1983.
  8. T. A. Marsland, “A Review of Game-Tree Pruning”, 1986.
  9. David Levy, David Broughton and Mark Taylor, “The SEX Algorithm in Computer Chess”, 1989. Kunihito Hoki and Masakazu Muramatsu, “Efficiency of Three Forward-Pruning Techniques in Shogi”, 2012.
  10. The official SDCC site and the SourceForge project history.
  11. The official Git documentation, A Short History of Git.
  12. Tord Romstad, “The Universal Shogi Interface, draft 1”, January 24, 2007.
  13. Floodgate's official history page.
  14. ISO's ISO/IEC 9899:2011 lists December 2011 as its publication date.
  15. The official mGBA timeline.
  16. OpenAI, “Introducing Codex”, May 16, 2025.