Four scripts each carried a copy of the .env parser, and one copy lacked
the unquoting the others had, so vercel env pull output broke it. They
share scripts/lib/env.mjs now. The hand-edited region configuration
moves out of the boundary builder into scripts/lib/region-config.mjs,
where it no longer shares a name with the generated tree the sibling
scripts import. scripts/lib/assign-districts.mjs, which decides which
district a panorama is credited to, gets its first tests, against
synthetic polygons so they pin geometry rules rather than data. A
data:refresh command runs the whole pipeline, with its Mapillary cost
stated where it is invoked.
/api/daily opens an ordinary session on a panorama picked
deterministically from the day and cached in Redis for two days, so
the Postgres draw and the Mapillary lookup happen once a day. Days
roll over at midnight Vietnam time. The round is scored by /api/guess
like any other and counted under its own level in the statistics.
There is no daily leaderboard: the only identity is a cookie and a
localStorage name, so a dated board would be won by whoever opened the
most private windows. The browser keeps the finished round and replays
it on a revisit, and tracks the streak of consecutive days. The home
page carries the challenge card and the result dialog shares the
outcome as a squares line.
The debug routes returned panorama coordinates by id and by region to
anyone, which is the answer to a live round; in production they now
answer only a request carrying DEBUG_ACCESS_KEY, and the bbox tester
that proxied Mapillary's failing search is removed.
/api/guess checks the username against the one rule the name prompt
uses, rejects non-finite coordinates before consuming the session, and
names why a submit failed. Session ids that are not UUIDs are replaced
on new-game and rejected on skip.
Score boards are no longer trimmed to 200: trimming deleted the running
total of anyone below the cut, so once 200th place held more than one
round's points no new player could ever get on. Scores are added with
ZINCRBY so concurrent rounds under one name both count. The distance
record is best-effort after scoring, so a distance failure no longer
reports a scored round as unsaved.
Two Redis keys per day count rounds by picked level and score and
distinct players; npm run stats prints them.