Verbatim, not cleaned up

GameEngine.ts held two unrelated engines in one 621-line file. The split needed no design round at all, just a careful move and one very literal constraint.

The same review’s next pick was GameEngine.ts, 621 lines that had held the daily engine and the duel engine side by side since early on, sharing a file without ever actually sharing code. Unlike the review’s earlier picks, this one skipped the multi-candidate design round entirely, because a quick pass over the file’s own six exports found something the earlier splits had to work harder to establish: zero consumer overlap. Every daily function was imported only by daily’s own store and its server-side replay validator; every duel function only by the duel store, PvP’s turn processor, and the duel-specific turn engine. No file in the whole codebase imported from both halves, and none imported a type from the file at all, only its functions. That fact turned “should this split” from a judgment call into something closer to a measurement.

One detail in that same recon pass would have tripped up a split done by simply cutting the file at a line number: a helper function sitting at the very top of the file, above every daily function, turned out to be duel-only.

function appendWordHistoryEntries(
  entries: WordHistoryEntry[],
  word: string,
  combos: ComboResult[]
): WordHistoryEntry[]

WordHistoryEntry[] exists nowhere but the duel game state, so position in the file said nothing reliable about which half the function belonged to; only tracing what actually called it did.

The harder constraint wasn’t architectural at all. The daily engine replays a deterministic sequence of pseudo-random calls to reproduce each day’s challenge, and the order those calls happen in is part of the contract, not an implementation detail, changing it would diverge existing players’ daily state. So every function moved into its new file exactly as written, verified byte-for-byte against the version still sitting on the main branch, rather than tidied up along the way the way a normal refactor might be tempted to.

Deleting the old file outright, rather than leaving it behind as a thin re-export, was the one real decision the map had to make, and it surfaced a gap the ticket’s own recon had missed: eleven test files beyond the three it had named turned out to import fixture-building functions from the old path too, once that path stopped existing. All eleven needed their imports moved, one of them split into two separate import statements since it drew from both the daily and duel sides at once, necessary fallout of the delete rather than scope the ticket had signed up for. A two-axis code review’s only real finding afterward was a documentation table that still named the file that no longer existed.