Pin seed->word across both languages with a shared hash
engine.ts used mulberry32 and engine.py used random.Random(seed). Same seed, different word — so every ?seed= permalink on the site would have shown a different puzzle than the recorded run it claimed to be replaying, and nobody would have noticed until someone checked one by hand. Both now derive the index from FNV-1a 32-bit over the decimal seed. A hash rather than a PRNG because there is no honest one-line JavaScript equivalent of Mersenne Twister, and this way there is nothing to keep in step: both sides compute the same integer from the same string. Math.imul on the JS side is load-bearing — a plain multiply overflows into a double and diverges after the first few bytes. Twelve seeds are pinned as a vector in both test suites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
This commit is contained in:
@@ -72,6 +72,21 @@ def test_seeded_answer_is_deterministic() -> None:
|
||||
assert answer_for_seed(1) != answer_for_seed(2)
|
||||
|
||||
|
||||
# Pinned so the browser cannot drift. `src/demos/wordle/__tests__/engine.test.ts`
|
||||
# asserts the same twelve words for the same twelve seeds. If a language's
|
||||
# built-in RNG were used instead of the shared hash, these two lists would
|
||||
# differ and every ?seed= permalink would show a different word than the
|
||||
# recorded run it claims to replay.
|
||||
SEED_VECTORS = [
|
||||
"wants", "amber", "spume", "toady", "divot", "filly",
|
||||
"bobby", "clews", "hikes", "lawns", "wreak", "twist",
|
||||
]
|
||||
|
||||
|
||||
def test_seed_vectors_match_the_typescript_port() -> None:
|
||||
assert [answer_for_seed(s) for s in range(12)] == SEED_VECTORS
|
||||
|
||||
|
||||
def test_rejections_do_not_consume_a_row() -> None:
|
||||
game = Game(seed=3, answer="tares")
|
||||
assert game.play("zzzzz")[1] is not None # not a word
|
||||
|
||||
Reference in New Issue
Block a user