eleusis / index.html
nph4rd's picture
Upload index.html with huggingface_hub
1caf272 verified
Raw
History Blame Contribute Delete
16.7 kB
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Eleusis</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.27.3/full/pyodide.js"></script>
<style>
:root { --cream:#f5deb3; --mist:#9fbfb2; --ink:#e8e3d6; }
* { box-sizing: border-box; }
body {
margin:0; min-height:100vh; font-family: "Avenir Next", "Segoe UI", system-ui, sans-serif;
background: linear-gradient(160deg,#0b3d2e 0%,#0a2b21 55%,#061a14 100%); color: var(--ink);
}
h1 { text-align:center; color:var(--cream); font-size:2.3em; margin:18px 0 2px;
text-shadow:0 0 18px rgba(245,222,179,.45); letter-spacing:.35em; }
.subtitle { text-align:center; color:var(--mist); margin:0 14px 18px; }
.wrap { display:flex; flex-wrap:wrap; gap:16px; max-width:1100px; margin:0 auto; padding:0 14px 40px; }
.panel { background:rgba(255,255,255,.05); border:1px solid rgba(245,222,179,.35);
border-radius:12px; padding:16px; flex:1 1 420px; }
.panel h3 { margin:2px 0 8px; color:var(--cream); font-weight:600; }
.chip { display:inline-block; background:#faf6ee; border:1px solid #b9ad93; border-radius:7px;
padding:4px 9px; margin:2px; font-weight:700; font-family:ui-monospace,monospace;
box-shadow:0 2px 4px rgba(0,0,0,.35); }
.red { color:#c0392b; } .blk { color:#1c2833; }
.rej { opacity:.55; transform:scale(.85); }
.hand button { background:#faf6ee; border:2px solid #b9ad93; border-radius:8px; padding:7px 11px;
margin:3px; font-weight:700; font-family:ui-monospace,monospace; font-size:1.02em;
cursor:pointer; }
.hand button.sel { border-color:#e67e22; outline:3px solid rgba(230,126,34,.45); }
.btnrow { display:flex; flex-wrap:wrap; gap:8px; margin:12px 0; }
.btn { border:none; border-radius:9px; padding:10px 14px; font-size:1em; cursor:pointer;
background:#28483d; color:var(--ink); border:1px solid rgba(245,222,179,.3); }
.btn.primary { background:#e67e22; color:#161616; font-weight:700; }
.btn:disabled { opacity:.4; cursor:default; }
textarea { width:100%; background:rgba(0,0,0,.35); color:#ffd9a0; border:1px solid rgba(245,222,179,.3);
border-radius:8px; padding:8px; font-family:ui-monospace,monospace; font-size:.95em; min-height:52px; }
.status { margin:10px 0; line-height:1.45; }
.status code, .log code, .modelview code { background:rgba(0,0,0,.35); color:#ffd9a0; padding:1px 5px;
border-radius:5px; font-size:.9em; white-space:pre-wrap; }
.log div { margin:3px 0; }
details { margin-top:10px; color:var(--mist); }
details code { color:#ffd9a0; }
.attempts button { background:rgba(0,0,0,.25); color:var(--ink); border:1px solid rgba(245,222,179,.25);
border-radius:7px; padding:6px 9px; margin:2px; cursor:pointer; }
.attempts button.sel { border-color:#e67e22; }
.modelview { margin-top:10px; line-height:1.5; }
.banner { background:rgba(230,126,34,.12); border:1px solid rgba(230,126,34,.5); border-radius:9px;
padding:10px 12px; margin:10px 0; }
a { color:#9fd3ff; }
#loading { text-align:center; padding:60px 20px; color:var(--mist); font-size:1.1em; }
.spin { display:inline-block; width:16px; height:16px; border:3px solid rgba(245,222,179,.3);
border-top-color:var(--cream); border-radius:50%; animation:s 1s linear infinite; vertical-align:-3px; margin-right:8px;}
@keyframes s { to { transform:rotate(360deg); } }
.warn { color:#e6b35a; font-size:.92em; }
.variants { display:flex; justify-content:center; gap:0; margin:0 auto 6px; width:fit-content;
border:1px solid rgba(245,222,179,.35); border-radius:10px; overflow:hidden; }
.variants button { background:rgba(255,255,255,.05); color:var(--mist); border:none;
padding:8px 16px; font-size:.98em; cursor:pointer; }
.variants button.sel { background:#e67e22; color:#161616; font-weight:700; }
.variantdesc { text-align:center; color:var(--mist); font-size:.92em; margin:0 14px 16px; }
</style>
</head>
<body>
<h1>β™  ELEUSIS β™₯</h1>
<p class="subtitle">A hidden rule decides which cards are accepted. Discover it β€” and see whether you beat a 4B model trained on this game.</p>
<div class="variants" id="variants" style="display:none">
<button id="var-hf" class="sel">πŸ† Benchmark rules</button>
<button id="var-small">πŸŽ“ Simple rules</button>
</div>
<p class="variantdesc" id="variantdesc" style="display:none"></p>
<div id="loading"><span class="spin"></span>Loading the game engine (Python in your browser, ~10s first time)…</div>
<div class="wrap" id="ui" style="display:none">
<div class="panel">
<div id="board" class="status"><i style="color:var(--mist)">Press <b>New game</b> to draw a hidden rule.</i></div>
<div id="handlabel" style="color:var(--mist);margin:8px 0 2px">Your hand β€” pick a card to test</div>
<div class="hand" id="hand"></div>
<div id="nlrow" style="display:none">
<div style="color:var(--mist);margin:12px 0 4px">Describe your rule guess in plain English:</div>
<div style="display:flex;gap:8px">
<input id="nl" type="text" maxlength="300" placeholder="e.g. every card must be higher than the previous one"
style="flex:1;background:rgba(0,0,0,.35);color:var(--ink);border:1px solid rgba(245,222,179,.3);border-radius:8px;padding:8px;font-size:.95em" />
<button class="btn" id="translate">✨ Translate</button>
</div>
<div id="nlstatus" class="warn" style="margin:4px 0"></div>
</div>
<div style="color:var(--mist);margin:12px 0 4px">Rule guess as Python (free, checked every turn):</div>
<textarea id="guess" placeholder='e.g. card.color == "red" or not mainline or card.rank > mainline[-1].rank'></textarea>
<div class="btnrow">
<button class="btn primary" id="newgame">πŸƒ New game</button>
<button class="btn" id="play" disabled>β–Ά Play selected card</button>
<button class="btn" id="noplay" disabled>βˆ… No card playable</button>
<button class="btn" id="giveup" disabled>🏳 Give up</button>
</div>
<div class="status" id="status"></div>
<div class="log" id="log"></div>
<details>
<summary>πŸ“– Rule-guess cheatsheet</summary>
<p>Write a Python expression (or multi-line body with <code>return</code>) over:<br>
<code>card.rank</code> (A=1 … K=13) Β· <code>card.suit</code> (<code>'hearts' 'diamonds' 'clubs' 'spades'</code>) Β·
<code>card.color</code> (<code>'red' 'black'</code>) Β· <code>card.is_face</code><br>
<code>mainline</code> — the accepted cards so far, oldest→newest.</p>
<p>Examples: <code>card.rank % 2 == 0</code> Β· <code>card.suit == "spades"</code> Β·
<code>not mainline or card.color != mainline[-1].color</code></p>
<p>Guesses are judged by <i>behavioral equivalence</i> (same accepts/rejects on every probe board) β€”
the exact check the model faced.</p>
</details>
</div>
<div class="panel">
<h3>πŸ€– The model's game</h3>
<p style="color:var(--mist);margin:4px 0"><i>Same rule, same deal (starter, hand, draw order). Careful: revealing may spoil the rule.</i></p>
<div class="attempts" id="attempts"></div>
<div class="modelview" id="modelview"></div>
<p style="color:var(--mist);font-size:.92em" id="modelnote"></p>
</div>
</div>
<script>
const GLYPH = {H:"β™₯", D:"♦", C:"♣", S:"β™ "};
const chip = (sym, rej=false) => {
const suit = sym.slice(-1), color = (suit==="H"||suit==="D") ? "red" : "blk";
return `<span class="chip ${color}${rej?" rej":""}">${sym.slice(0,-1)}${GLYPH[suit]}</span>`;
};
const esc = s => (s||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
let py, selected = null, gameOn = false, variant = "hf";
const WORKER_URL = "https://pinference.arturomf94.workers.dev";
const VARIANT_INFO = {
hf: {
desc: "26 held-out benchmark rules Β· 10 turns Β· a played card is replaced from the deck.",
note: `Model: <a href="https://huggingface.co/nph4rd/Qwen3.5-4B-eleusis-rl4-consistency-step30" target="_blank">
Qwen3.5-4B-eleusis-rl4-consistency-step30</a> β€” oracle SFT + GRPO with consistency-shaped rewards.
Its games are replays from the recorded 26Γ—8 verification run (512 tokens/turn, temp 0.7), not live inference.`,
},
small: {
desc: "6 held-out simple rules Γ— 4 deals Β· 8 turns Β· no redraws β€” your hand shrinks as you play.",
note: `Model: a Qwen3.5-4B LoRA (rank 16) trained with RL in
<a href="https://app.primeintellect.ai/dashboard/training/r9gjcjph7qwgq0c44byyyfg9" target="_blank">this hosted run</a>
on the simple-rules variant. Its games are replays from the recorded step-90 eval
(24 deals Γ— 8 attempts, 1024 tokens/turn, temp 0.7), not live inference.`,
},
};
function setVariant(v) {
variant = v;
document.getElementById("var-hf").classList.toggle("sel", v === "hf");
document.getElementById("var-small").classList.toggle("sel", v === "small");
document.getElementById("variantdesc").innerHTML = VARIANT_INFO[v].desc;
document.getElementById("modelnote").innerHTML = VARIANT_INFO[v].note;
// drop any in-progress game: rules and replays no longer match
gameOn = false; selected = null;
document.getElementById("board").innerHTML =
`<i style="color:var(--mist)">Press <b>New game</b> to draw a hidden rule.</i>`;
document.getElementById("hand").innerHTML = "";
document.getElementById("status").innerHTML = "";
document.getElementById("log").innerHTML = "";
document.getElementById("attempts").innerHTML = "";
document.getElementById("modelview").innerHTML = "";
["play","noplay","giveup"].forEach(id => document.getElementById(id).disabled = true);
}
async function boot() {
py = await loadPyodide();
const files = ["cards.py", "engine.py", "game.py"];
for (const f of files) {
const src = await (await fetch("./" + f)).text();
py.FS.writeFile(f, src);
}
for (const v of ["hf", "small"]) {
const games = await (await fetch(`./games_${v}.json`)).text();
py.globals.set("_games_payload", games);
py.globals.set("_variant", v);
await py.runPythonAsync(`import game\ngame.load_games(_variant, _games_payload)`);
}
document.getElementById("loading").style.display = "none";
document.getElementById("ui").style.display = "flex";
document.getElementById("variants").style.display = "flex";
document.getElementById("variantdesc").style.display = "block";
setVariant("hf");
}
function call(expr) { return JSON.parse(py.runPython(expr)); }
function renderHand(hand, enabled) {
const el = document.getElementById("hand");
el.innerHTML = "";
hand.forEach(sym => {
const b = document.createElement("button");
const suit = sym.slice(-1);
b.innerHTML = `<span class="${(suit==="H"||suit==="D")?"red":"blk"}">${sym.slice(0,-1)}${GLYPH[suit]}</span>`;
b.disabled = !enabled;
b.onclick = () => { selected = sym; [...el.children].forEach(c => c.classList.remove("sel")); b.classList.add("sel"); };
el.appendChild(b);
});
selected = null;
}
function render(st) {
gameOn = !st.over;
const potential = st.max_turns - st.turn;
let bh = `<b style="color:var(--mist)">Board:</b> `;
st.board.forEach(e => { bh += chip(e.card); e.rejects.forEach(r => bh += chip(r, true)); });
bh += `<div>Turn <b>${st.turn}/${st.max_turns}</b> Β· score if you solve it this turn: <b>${potential}</b></div>`;
document.getElementById("board").innerHTML = bh;
renderHand(st.hand, gameOn);
["play","noplay","giveup"].forEach(id => document.getElementById(id).disabled = !gameOn);
let msg = st.message ? `<span class="warn">${esc(st.message)}</span>` : "";
if (st.log.length) {
const last = st.log[st.log.length-1];
if (last.guess && !last.guess_correct && !st.over)
msg += `<div class="warn">Your guess didn't match the hidden rule${last.guess_error ? " ("+esc(last.guess_error)+")" : ""} β€” keep refining.</div>`;
}
if (st.over) {
const mdl = `The model solved this rule in <b>${st.model_solved_count}/8</b> recorded attempts` +
(st.model_best_turns ? ` (best: ${st.model_best_turns} turns).` : ".");
msg = `<div class="banner">${st.solved
? `πŸŽ‰ <b>Solved in ${st.turn} turns β€” score ${st.score}/${st.max_turns}</b>`
: `<b>Game over β€” the rule eluded you (score 0).</b>`}<br>
Hidden rule: <i>${esc(st.rule_label)}</i><br><code>${esc(st.rule_code)}</code><br>${mdl}</div>`;
}
document.getElementById("status").innerHTML = msg;
document.getElementById("log").innerHTML = st.log.map((e,i) => {
const badge = {accepted:"βœ… accepted", rejected:"❌ rejected",
correct_no_play:"βœ” correct no-play", missed_play:"βœ— missed play (a card was playable)"}[e.result] || e.result;
const g = e.guess ? ` Β· guessed <code>${esc(e.guess)}</code> β†’ ${e.guess_correct?"πŸŽ‰ CORRECT":"wrong"}` : "";
return `<div>T${i+1}: ${e.card?chip(e.card):"<i>no card</i>"} β†’ ${badge}${g}</div>`;
}).join("");
const at = document.getElementById("attempts");
at.innerHTML = "";
(st.model_attempts||[]).forEach((a,i) => {
const b = document.createElement("button");
b.textContent = `Attempt ${i+1} ${a.solved?"βœ…":"❌"}`;
b.onclick = () => { [...at.children].forEach(c=>c.classList.remove("sel")); b.classList.add("sel"); reveal(i); };
at.appendChild(b);
});
document.getElementById("modelview").innerHTML =
`<i style="color:var(--mist)">Select an attempt to reveal the model's full game.</i>`;
}
function reveal(i) {
const ep = call(`game.reveal_attempt(${i})`);
let h = `<p>The model solved this rule <b>${ep.model_solved_count}/8</b> times. ` +
`<b>Attempt ${i+1}</b> β€” ${ep.solved ? "βœ… solved in "+ep.turns_used+" turns" : "❌ not solved"}.</p>`;
ep.turns.forEach((t,k) => {
const badge = {accepted:"βœ…", rejected:"❌", correct_no_play:"βœ” no-play",
missed_play:"βœ— missed", invalid:"⚠ invalid (hallucinated card)"}[t.result] || t.result;
const solved = t.solved_here ? " β€” <b>πŸŽ‰ hypothesis correct, game over</b>" : "";
h += `<div style="margin:6px 0"><b>T${k+1}</b>: ${t.card?chip(t.card):"<i>no card</i>"} ${badge}${solved}<br>
<code>${esc(t.rule)}</code></div>`;
});
document.getElementById("modelview").innerHTML = h;
}
document.getElementById("newgame").onclick = () => render(call(`game.new_game("${variant}")`));
document.getElementById("var-hf").onclick = () => setVariant("hf");
document.getElementById("var-small").onclick = () => setVariant("small");
document.getElementById("play").onclick = () => {
if (!selected) { document.getElementById("status").innerHTML = `<span class="warn">Pick a card first.</span>`; return; }
const g = document.getElementById("guess").value;
py.globals.set("_sel", selected); py.globals.set("_guess", g);
render(call("game.play_turn(_sel, _guess, False)"));
document.getElementById("guess").value = "";
};
document.getElementById("noplay").onclick = () => {
const g = document.getElementById("guess").value;
py.globals.set("_guess", g);
render(call('game.play_turn("", _guess, True)'));
document.getElementById("guess").value = "";
};
document.getElementById("giveup").onclick = () => render(call("game.give_up()"));
if (WORKER_URL) document.getElementById("nlrow").style.display = "block";
document.getElementById("translate").onclick = async () => {
const nl = document.getElementById("nl").value.trim();
const st = document.getElementById("nlstatus");
if (!nl) { st.textContent = "Describe the rule first."; return; }
st.textContent = "Translating…";
try {
const r = await fetch(WORKER_URL, { method: "POST",
headers: { "content-type": "application/json" }, body: JSON.stringify({ text: nl }) });
const data = await r.json();
if (!r.ok || !data.predicate) throw new Error(data.error || "no result");
py.globals.set("_cand", data.predicate);
const compiles = py.runPython(
"import engine\ntry:\n engine.compile_python_rule(_cand)\n _ok = True\nexcept Exception:\n _ok = False\n_ok");
if (!compiles) { st.textContent = "The translation didn't compile β€” try rephrasing."; return; }
document.getElementById("guess").value = data.predicate;
st.textContent = "Translated βœ“ β€” review the Python below, then play a card (or no-play) to submit it.";
} catch (e) {
st.textContent = "Translation failed: " + e.message;
}
};
boot();
</script>
</body>
</html>