Fourteen doors, seeded once. In password mode you SUBMIT or DENY each one
by eye — a lookalike differs from the genuine door by a shrinking tell (a
hinge a few pixels off, a plaque a shade off-hue), and it keeps getting
subtler the deeper you go. Hesitate past the decision window and it
auto-submits, because that’s what people on autopilot actually do. Submitting
to a fake isn’t a scratch, it’s the whole run: the password is the entire
authority, so one bad call hands it over and the run ends right there. In
passkey mode the only input is APPROACH — a sealed die challenge-responds
to the door itself, and simply won’t stamp a fake one no matter how good it
looks. Switch modes (M) and you replay the identical sequence of doors —
same genuine/fake pattern, same shrinking tells — so the comparison is exact,
not vibes.
The point isn’t that passkey-mode-you is more careful. It’s that passkey mode never asks you to be: the skill password mode trains — reading the tell — is worth nothing once the credential itself checks the door instead of your eye checking the credential. Riffs on the WebAuthn ceremony: phishing resistance is a property of the credential, not of the user’s attention.
Reuse
The mechanic lives in src/ceremony.js as a framework-free ES module:
createCeremony(opts)— returns a state object; callupdate(dt)each frame, andsubmit()/deny()(password mode) orapproach()(passkey mode) on input.opts:seed,totalDoors,genuineCount,tellStart,tellEnd,decisionWindow,mode('password'|'passkey').makeLevel(seed, total, genuineCount, tellStart, tellEnd)is exported separately — it’s the pure level generator, useful if you want to render a level preview without spinning up a full state machine.- Rendering is yours: the state exposes
doors(each withgenuineandtell),index(the current door),doorTimer,status,reason, andstats. The demo draws all of it on canvas.
Gotchas
createCeremony({ seed, mode })with the sameseedacross both modes is the whole point — it’s what makes “same level, different credential” true rather than asserted. The demo’s mode toggle (M) reusesseedfor exactly this reason; onlyN(new level) changes it.telldecreases linearly across the door sequence by index, not by genuine/fake status — so it’s a property of “how deep in the corridor,” not of any single door. A genuine door’s appearance never varies; only fakes drift toward it astellapproachestellEnd.- In password mode, an idle player isn’t safe —
update(dt)auto-submits the current door oncedoorTimerhitsdecisionWindow. In passkey mode there’s no timer at all;approach()is the only action and nothing times out. - The demo in
demo/bundles its own copy of the module (self-contained by contract). If you touchsrc/, re-copy it intodemo/.