Obfuscate the daily-answer sequence to deter cheating
The ordered answer list shipped as plaintext English words in
src/data/words.*.json, so anyone could open the JSON — in the repo or
the JS bundle / network tab — find their puzzle number, and read today's
or any future day's word. answers[puzzleNumber-1] was literally the
word.
Ship the sequence as `answersEnc` instead: the ordered list joined,
XOR'd against a fixed key, and base64-encoded (src/engine/obfuscate.ts),
decoded once at module load in words.ts. One isomorphic module is shared
by the build script and the runtime so the two can never drift.
This is a deliberate speed bump, not encryption. With no backend the
full list must reach every browser to compute the word offline, and the
XOR key ships in the bundle, so a determined reader can still recover it.
The goal is only to defeat the trivial "read the list / grep for the
word" cheat — encoded, the list is no longer human-readable or
greppable.
The encoding decodes back to byte-identical words, so the per-puzzle
mapping is unchanged: recorded results (keyed by <lang>-<puzzleNumber>)
and in-progress local games from before the change still match. A new
guard in tests/words.test.ts pins known puzzle->word pairs so any future
renumbering of history fails loudly.
The committed JSON was re-emitted from the existing answers, not
re-fetched from the upstream sources, so word-list drift can't silently
renumber history as part of this change.