Build AT Mot: bilingual, database-free daily word game on atproto
AT Mot is a static client-side word game native to the AT Protocol. There is
no application database and no bespoke backend: player data lives in each
player's PDS, and the leaderboard is read from the public Constellation
backlink index.
What's included:
- Lexicons under bzh.herve.atmot.* (result, stats, defs) following the Lexicon
Style Guide, with a format linter and an idempotent publish script.
- Deterministic UTC daily word engine for EN + FR, with self-curated word lists
from public-domain/MIT sources (ENABLE, an-array-of-french-words, hermitdave
frequency) — French sourced independently of wordle-fr. Frozen epoch
2026-06-23, frozen puzzleTarget format via a single shared helper.
- Browser OAuth (atcute), write-once result records + a mutable self stats /
declaration record, all written client-side to the player's PDS.
- "Share to atproto": app.bsky.feed.post with a standard emoji grid and the
puzzle permalink as a real link facet + embed (so Constellation can discover
it). Leaderboard + per-puzzle pages aggregate Constellation backlinks in the
browser with caching and graceful degradation; live likes/reposts/replies.
- Clean minimal-nerdy UI: own brand board palette (teal/amber/slate, not
Wordle's), SkyPress fonts (Overused Grotesk + IBM Plex Mono), prefers-color-
scheme dark mode, a11y (per-tile aria, non-hue cues), mobile-first.
- Cloudflare Pages target (SPA fallback, hosted client-metadata.json).
- Dual MIT/Apache-2.0, README/NOTICE/DECISIONS, 41 passing tests, 0 audit
vulnerabilities.
Build AT Mot: bilingual, database-free daily word game on atproto
AT Mot is a static client-side word game native to the AT Protocol. There is
no application database and no bespoke backend: player data lives in each
player's PDS, and the leaderboard is read from the public Constellation
backlink index.
What's included:
- Lexicons under bzh.herve.atmot.* (result, stats, defs) following the Lexicon
Style Guide, with a format linter and an idempotent publish script.
- Deterministic UTC daily word engine for EN + FR, with self-curated word lists
from public-domain/MIT sources (ENABLE, an-array-of-french-words, hermitdave
frequency) — French sourced independently of wordle-fr. Frozen epoch
2026-06-23, frozen puzzleTarget format via a single shared helper.
- Browser OAuth (atcute), write-once result records + a mutable self stats /
declaration record, all written client-side to the player's PDS.
- "Share to atproto": app.bsky.feed.post with a standard emoji grid and the
puzzle permalink as a real link facet + embed (so Constellation can discover
it). Leaderboard + per-puzzle pages aggregate Constellation backlinks in the
browser with caching and graceful degradation; live likes/reposts/replies.
- Clean minimal-nerdy UI: own brand board palette (teal/amber/slate, not
Wordle's), SkyPress fonts (Overused Grotesk + IBM Plex Mono), prefers-color-
scheme dark mode, a11y (per-tile aria, non-hue cues), mobile-first.
- Cloudflare Pages target (SPA fallback, hosted client-metadata.json).
- Dual MIT/Apache-2.0, README/NOTICE/DECISIONS, 41 passing tests, 0 audit
vulnerabilities.
Build AT Mot: bilingual, database-free daily word game on atproto
AT Mot is a static client-side word game native to the AT Protocol. There is
no application database and no bespoke backend: player data lives in each
player's PDS, and the leaderboard is read from the public Constellation
backlink index.
What's included:
- Lexicons under bzh.herve.atmot.* (result, stats, defs) following the Lexicon
Style Guide, with a format linter and an idempotent publish script.
- Deterministic UTC daily word engine for EN + FR, with self-curated word lists
from public-domain/MIT sources (ENABLE, an-array-of-french-words, hermitdave
frequency) — French sourced independently of wordle-fr. Frozen epoch
2026-06-23, frozen puzzleTarget format via a single shared helper.
- Browser OAuth (atcute), write-once result records + a mutable self stats /
declaration record, all written client-side to the player's PDS.
- "Share to atproto": app.bsky.feed.post with a standard emoji grid and the
puzzle permalink as a real link facet + embed (so Constellation can discover
it). Leaderboard + per-puzzle pages aggregate Constellation backlinks in the
browser with caching and graceful degradation; live likes/reposts/replies.
- Clean minimal-nerdy UI: own brand board palette (teal/amber/slate, not
Wordle's), SkyPress fonts (Overused Grotesk + IBM Plex Mono), prefers-color-
scheme dark mode, a11y (per-tile aria, non-hue cues), mobile-first.
- Cloudflare Pages target (SPA fallback, hosted client-metadata.json).
- Dual MIT/Apache-2.0, README/NOTICE/DECISIONS, 41 passing tests, 0 audit
vulnerabilities.
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.