part 20
The daily challenge in a strip and six games in progress at once
The big tile of the daily challenge turned into a low strip and a window, the game remembers the result of the day and each kind of game has a save slot and a lock of its own. How it was verified that no game in progress was lost in the move.
The tile of the daily challenge was the biggest on the home screen and yet it did not say the one thing that the player asks of it: whether today is done. The assignment of 23 and 24 October 2026 came from one look at the screen. The tile had the date, the setting, the points and the whole rule of the day, it broke the grid, the puzzle of the day was only a badge on the Puzzle Labs tile and only the last game in progress was ever offered. Five parts of work and one big question arose from that: what happens to a game that the player has in progress when the way it is saved changes?
This article is a record of how it was solved. The last part deserves attention above all: moving saved games is exactly the kind of change in which one mistake is enough and the player loses a mission in progress or a paid attempt.
A strip instead of a tile
The tile of the daily challenge is gone. Above the tiles there is now one low button across the whole width: "Daily challenges · 24 October", the count "Today 1 / 2 done" and a chip for each daily activity. Below 560 px it wraps into two rows. A click or Enter opens a window with a list: a row has an icon, a name, the date of the day, a sentence about the state ("Done: won in round 23, 108 pts"), the setting of the day, the button ⓘ and the buttons Play, Continue or Play again according to the state.


More important than the looks is how the activities are written down. Each is one entry in the list Dailies.LIST in the file js/data/dailies.js: an identifier, an icon, a name, a state function, a buttons function and a text for ⓘ. The state functions are pure, so they know nothing about the application and are tested separately. Things that the game cannot know from the data (a game in progress in the slot "daily", the setting of the day) are supplied in a context. Adding a third daily activity is therefore one entry in the list plus one piece of handler code in the application, and a test fails if an entry has no handler code or vice versa.
| activity | icon | when it appears |
|---|---|---|
| Daily challenge | 📅 | from the start (after the welcome card) |
| Puzzle of the day | 🧪 | together with the Puzzle Labs tile |
The strip is not shown at all if no daily activity is revealed. A player who has the daily challenge and does not have the labs yet sees one chip.
What the game remembers about the day
Until then the game saved only the days that were won (for the achievements). It therefore could not say "today: a loss in round 14". The new field profile.dailyGame holds the last 14 days on which the challenge was played: the day, the result (a win, a win on points, a loss, a loss on points, a draw), the round, the score, the number of finished games and the number of wins. There is one rule: the round and the score belong to the first win of the day, if there is one, otherwise to the last game. A loss thus shows ✗ until a win replaces it, and a win is never taken back by any later loss.
The entry is written in the same mutation of the profile as the game itself, so one game cannot be counted twice. And nothing that was paid out before changes: the first win of the day for the rewards and achievements is read by the functions from ach.days, as until now. An older profile gets the days filled in from what it already contains: every day from ach.days (at most the last 14) becomes a win, with the round and score from the history if an entry exists there. Today's loss cannot be filled in from anywhere, and so it is not guessed.
The rule of the day
Since 22 October the daily challenge has the rule "Long wars end" (the melting of stopped stacks after three rounds without a change of owner and an offer to end a stalemate). Before, its sentence was directly on the tile. Now it is in two places: in the ⓘ at the challenge and in the window Rules of this game as a separate section "Rule of the day", when a daily game is running. The numbers in the sentence are taken from Settings.WEAR, so the text and the rule cannot drift apart. A game from an older save that does not have such a rule does not show the section.
Six kinds, six places
The biggest part of the work could not be seen. Until then the game had one place for a game in progress. Whoever started Principalities[1] overwrote a campaign mission in progress, and whoever had a paid attempt at an Uber mission or an Iron Run mission and started Principalities lost the attempt. In addition two tabs could not play two different games, because the lock "one game – one tab" was the only one.
Six kinds were decided: classic, free, daily, mini, campaign and iron. A rematch with a boss, an Uber mission and the Golden Goblin belong to the campaign, regardless of the sector. A puzzle, a lesson, a watched game and a game opened from a snapshot occupy no place, as until now.

| decision | why |
|---|---|
six keys audamir.save.<kind>, not one key with a map | two tabs that play two kinds write to two keys; one key would be a read-change-write cycle in which a game gets lost |
the kind of a place is its key, Store.loadSave(kind) verifies it against meta | a hand-made file must not put a minigame into the place of Principalities |
a lock per kind: Web Lock <prefix>.game.<kind>, without Web Locks a lease audamir.lock.<kind> | everything that was already proved for one lock holds for each |
| a tab owns at most one kind (the kind of the game it plays) | a tab that only looks owns nothing |
| a paid attempt is asked about only in a game of the kind in which it waits | a game of another kind no longer takes the attempt away |
The consequence is visible: Principalities can be played while a paid attempt at an Uber mission waits. Two tabs can play two kinds at once and the question "A game is open in another tab" is asked only for the same kind. The row of a kind that another tab owns says "Open in another tab" and offers only "Take over here".
The list of unfinished games on the home screen shows three rows (the most recently saved first) and then "Show all (n)". The decisions that the assignment left open are marked in the design as defaults that I can change: a new game of a kind replaces a game in progress of the same kind without a question (as until now), the record of the day is kept for 14 days while games in progress are not limited in time, and the puzzle of the day is revealed together with Puzzle Labs.
The extent of the change: 51 files, 1,668 added and 384 removed lines. The strip and the window with the data had 25 files. The rules of the game (rules.js, ai.js, engine.js, flow.js, mods.js) did not change, and that was checked: node tools/baseline.js (172 fingerprints) and node tools/calibrate.js --verify (18 measurements) came out identical.
How it was proved that nothing was lost
The old single key audamir.save has to be moved to the place of its kind once, at the start of the page, before anything draws it. The procedure is "write first, then remove": if the write failed, the old game stays. If a game is already in the place of the kind, the newer one by the time of saving stays, and a game without a time is the oldest. If there is room for only one copy, the old key is removed and the write is tried again, and on failure it is put back. An unreadable value is discarded and the old lock record disappears. The migration pays nothing, it only moves the value.
Here a rule applies that the project gained in the renaming of identifiers: hand-written samples pass while real data show the error. That is why tools/daily-hub-migration.js uses the old code. From the state of the game just before the change it makes a real history in a fake DOM: wins and losses in Principalities, a free game, a daily challenge won on several days and a game in progress of each of the six kinds in the single place of the old code. Then the new application is run over each dump. What is verified:
- the game lies in the place of its kind byte by byte, the old key and the lock record are gone and the row is on the home screen,
- the profile after loading equals the profile of the old code (coins, experience, history, achievements, leaderboard), except the new field
dailyGame, - the record of the day contains four earlier days with the round and score from the history,
- Continue continues the same game (seed, mode, round) and in Principalities, free play and the daily challenge the game is played to the end with the same decisions as in the old code. The profile after it is exactly the one that the old code made, so nothing was paid twice.
What the independent review found
After it was built, the design was read by an independent check (24 Oct 2026, read only). On the main paths it found neither a loss nor a double payout, but it found five real problems. All were fixed; the three main ones:
- The lock was compared with the wrong game. The check of holding the place compared with the game that was there before the new one. If another tab took the lock while the start map was being made, the tab played the whole game and the write at the end was refused. The game that is starting is now sent into the check (a test in both lock mechanisms). The bug was inherited from the previous design of the lock.
- A game without a time overwrote a newer one. A game from the old key without the field
tscould overwrite a newer one in the place of the kind. Now a game without a time is the oldest and the migration writes before it removes. - **Every
storageevent from another tab redrew the home screen.** In a tab that was playing, the die of the avatar switched to golden in the middle of a campaign mission. Saving of another kind now does not change the data of this tab, only the list of unfinished games when the home screen is displayed.
Two further findings (a start that did not take place but held a claim, and a round taken from a damaged place without a check) were fixed too. Besides them the design names two limitations that remained.
What remained unverified and what remained open
- A stale profile in a frozen tab. Two tabs that play two kinds at once each have their own copy of the profile. A live tab catches up at every
storageevent, but a tab that the browser froze or put into the back-forward cache may write a stale profile after a game is finished. For the campaign and the Iron Run this window existed before, now it exists for Principalities and the daily challenge too. A cheap solution (reading the profile before writing the result) touches every end card, and so it was not done. - An old tab left open across an update. The old single key is moved once at the start of the page. If an old tab finishes it later, it leaves in the place a snapshot that the old code never deletes (it deletes only the old key). After an update the old tabs have to be refreshed.
- A real hover over ⓘ was not checked; hover in the browser panel did not set
:hover, and so it is covered only by a test. - The built version (
dist/web) was not run through a smoke test on this machine, because the Chromium in the snap package does not run here. - The texts in Czech, Serbian, Russian, Spanish, Portuguese, French and German are drafts, like the rest of these catalogues. The most important to read is the sentence about what a repeated win pays.
What I take from it
- What can be proved with the old code should not be proved with a hand-written sample. A check that makes the profile from a game in progress with the original code and plays it to the end with the new one reveals not only the loss of a game but also a double payout.
- A key per kind is better than one map. Two tabs that write to one key overwrite each other's games. Two tabs that write to two keys do not.
- Done does not mean the same for everyone. The assignment assumed that only the first win of the day counts, and it was measured that this holds for one part of the rewards and not for the other. Such things should be written down as a question, not quietly fixed.
- A check that depends on the date is not a check. For a long time it seemed that the migration failed now and then; the check was failing. More about it in the article about the mistakes of the week.
Sources and notes
- State: the design and the record of verification in the documentation of the game, as of 24 Oct 2026 (merged as version 0.49.1; the fix of the migration check on 26 Oct 2026). The description of the migration check and of the independent review is according to it.
- The extent of the changes (51 files, 1,668 added and 384 removed lines; the strip and the window 25 files) is lines according to
git show --statin the repository of the game, not a measure of work. - Not run when writing:
baseline.js(172 fingerprints) andcalibrate.js --verify(18 measurements) came out identical according to the documentation of the game, not again. - What is not in the article: the origin of the rule of the day "Long wars end" (22 Oct 2026); the articles of the series do not describe it.
- Series: the same procedure for renaming identifiers is in the article How to test a game that never stops changing, rewards and achievements in the article Achievements and the introduction.
What could not be verified: a real hover over ⓘ, the built version dist/web and the tools tools/daily-browser.js and tools/lock-browser.js (according to the documentation they were not run on this machine); the translations into seven further languages are drafts.
Notes
- Until version 0.65.6 of 31 October 2026 it was called Classic. ↩︎