Why tomorrow's word is not on your phone
A daily puzzle has a scheduling problem that most of them get wrong, and it is not obvious until someone points it out.
The mistake almost everyone makes
The natural way to build a daily word game is to ship the answer list inside the app and pick today's word with a function of the date. It is simple, it works offline, and it needs no server at all. It is also the moment you hand every player every future answer.
Anyone can unpack an app bundle. Once the list and the function are both on the device, the whole forward schedule is readable, permanently. No amount of obfuscation fixes this, because the device has to be able to compute the answer, which means the answer is there.
The only real fix
Do not ship the schedule. Pinteza asks a small server for one day's word, and the server refuses any date later than its own clock. That date check is the entire security boundary, and everything else is detail.
Trusting the client's date would make the whole exercise theatre, because setting the device clock forward is precisely the attack. So the check runs on the server, against the server's clock.
Two details that are easy to get wrong
Timezones. The game uses the player's local date, and New Zealand reaches a given day about thirteen hours before UTC midnight. An exact date match locks out everyone east of UTC for part of their day. The tolerance is today plus one, which costs you the keenest attacker seeing tomorrow's word slightly early, a vastly smaller leak than the entire forward schedule.
The past is the other half of the boundary. Leaving past dates open makes the forward limit decorative. If the schedule for a rotation is an array indexed by day number, anyone allowed to fetch arbitrary past dates can walk backwards over the array's length, reassemble it in order, and compute every future answer from it. A few minutes of scripting. Past answers being "already public" is true one at a time and false in bulk. Pinteza allows three days back, which is enough to absorb clock skew and a device sitting a day behind, and nothing more.
The incident that taught us the rest
The words live in a key-value store at the edge, uploaded from the game repository. At one point thirty-two answers were removed from the pools for content reasons, the client was rebuilt, and the upload was never repeated. The live server went on scheduling the removed words as future dailies while the verification script, which only reads the repository, cheerfully printed PASS.
Re-uploading alone would not have been enough either. Responses are cached at the edge for
a week, so the old words would have kept being served regardless. The fix is a
DATA_VERSION constant that forms part of the cache key: bump it and every
previously cached answer is retired atomically. Without something like it there is no way to
invalidate short of a manual purge.
What it costs
One request per player per day, cached at the edge because the response depends only on the date and the rotation. The daily mode needs a connection; every other mode still works offline. That is the trade, and for a game whose entire premise is that everyone plays the same word on the same day, it is worth it.



