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:
karti-ai
2026-08-28 15:42:17 -07:00
parent a56f097f28
commit 69607fbfe9
22 changed files with 3508 additions and 86 deletions
+71 -3
View File
@@ -316,17 +316,43 @@ export function literalAfter(src, anchor, open = '{') {
return null;
}
/** Marker key on the stand-in an unresolvable identifier evaluates to. */
export const UNRESOLVED = '__pigUnresolvedIdentifier__';
export const isUnresolved = (v) => Boolean(v) && typeof v === 'object' && UNRESOLVED in v;
/**
* A sandbox in which every free identifier resolves to a labelled stand-in.
*
* `RewardSpec.source.code` is legitimately an identifier — the Python is
* imported with `?raw` and cannot exist in plain Node — so the reward literal
* must be readable WITHOUT its `code`. Strict mode is still the default: a
* weight that turns out to be a stand-in is a contract failure, not a skip.
*/
function lenientSandbox() {
return new Proxy(Object.create(null), {
has: () => true,
get: (_target, key) => {
if (key === Symbol.unscopables) return undefined;
if (typeof key !== 'string') return undefined;
return { [UNRESOLVED]: key };
},
});
}
/**
* Evaluates a TypeScript object/array literal as plain data.
*
* `as const` and `satisfies T` are stripped from code spans only. Anything else
* a literal might carry — an identifier, a call, a spread of an import — throws,
* and callers turn that into a contract failure with the file named.
* and callers turn that into a contract failure with the file named. Pass
* `{lenient: true}` to get stand-ins for free identifiers instead.
*
* @param {string} text
* @param {string} label
* @param {{lenient?: boolean}} [options]
*/
export function evalLiteral(text, label) {
export function evalLiteral(text, label, options = {}) {
const js = segment(text)
.map((s) =>
s.code
@@ -338,7 +364,8 @@ export function evalLiteral(text, label) {
)
.join('');
try {
const value = vm.runInNewContext(`(${js})`, Object.create(null), { timeout: 2000 });
const sandbox = options.lenient ? lenientSandbox() : Object.create(null);
const value = vm.runInNewContext(`(${js})`, sandbox, { timeout: 2000 });
return { ok: true, value, error: null };
} catch (error) {
return {
@@ -421,6 +448,47 @@ export function loadAllMetas() {
return { metas, errors };
}
/** Every .ts/.tsx file that belongs to one demo. */
export function demoFiles(slug) {
return walk(path.join(DEMOS_DIR, slug), (f) => /\.tsx?$/.test(f));
}
/**
* Finds one named literal anywhere inside a demo's own source.
*
* A demo is free to put `reward` in `reward.ts` or inline it in `demo.tsx`;
* the contract is about the values, not the file layout. First match in
* filename order wins, and the file it came from is returned so failures can
* name it.
*
* @param {string} slug
* @param {RegExp[]} anchors
* @param {'{' | '['} open
* @param {string} label
* @param {{lenient?: boolean}} [options]
*/
export function findInDemo(slug, anchors, open, label, options = {}) {
for (const file of demoFiles(slug)) {
const src = read(file);
for (const anchor of anchors) {
let text = null;
try {
text = literalAfter(src, anchor, open);
} catch (error) {
return { file: rel(file), error: `could not brace-match the ${label} literal: ${error.message}` };
}
if (!text) continue;
const result = evalLiteral(text, label, options);
if (!result.ok) return { file: rel(file), error: result.error, text };
return { file: rel(file), value: result.value, text };
}
}
return {
file: null,
error: `no ${label} literal found in any .ts/.tsx file under ${rel(path.join(DEMOS_DIR, slug))}`,
};
}
/* ------------------------------------------------------------------ verticals */
export const VERTICALS_FILE = abs('src', 'content', 'verticals.ts');