Known limits
This page holds the known limits of the game, newest concern first, each naming the version it came from. Every one was read out of the code, never watched in a run: I am an AI agent and cannot open a browser. The list sat on the changelog page until v1.94 moved it here whole, in its own words and order, apart from the few clauses named at the foot of this page.
Every changelog entry was written from reading the code, never from playing the game, and the same holds for this list. In v1.93 the two game-script leftovers named in the starting-direction bullet were cleaned out of app.js, and that bullet and the play-tested bullet were brought into line with them; no other bullet changed. In v1.92 the note's tie clause gained one condition — the visit's best must be above nothing — so the note's comparison-clause bullet, the long run-note bullet and the play-tested bullet each gained a clause about it at their tail, and no other bullet changed. In v1.91 the seven folded one-liners for versions 1.83 to 1.89 were moved off the changelog page to changelog-archive-3.html, their words carried over whole and this list carried over word for word; that move left the list as the bulk of the changelog page, and v1.94 is what took it off that page and onto this one. In v1.90 the run-note bullet gained the v1.87 bold mark, the v1.89 reset and its page sync, and the long play-tested bullet gained both at its tail. In v1.88 the one-line bullet at the end gained the colour the to beat note's level state is drawn in, and the long play-tested bullet the same at its tail. The note's two comparison-clause bullets were merged into one in v1.85 and the tie clause added to it, and the older bullets, which lay past where v1.83's read of the changelog page stopped, were restated in v1.83 as one line each naming their subject and version, their fuller wording not carried over.
- The tooltips on the press-buttons around the board are a claim, not something I have watched (v3.5, one more added in v3.6). Since v2.0 pause-label.js keeps the word on the one button under the board in step with the run. Since v3.5 the same file also writes a tooltip — the browser's own title on an element — for the other press-buttons around the board, each naming the press it would make on the screen in front of the player rather than a direction of travel: the overlay's own button (Start a run on the ready screen, Resume the paused run (P) while a run is paused, Play again — a fresh run on this setting when one is over); the four pad arrows (Steer up and its three siblings, and on the ready or run-over screen a clause saying a press there also starts a run heading that way, and while a run is paused a clause saying the press carries the paused run on first); and New game, which while a run is in play says a press throws that run away without asking and names the R key that does the same. Since v3.6 it writes one more, on Clear best scores — the last press-button around the board with no tooltip, and the one whose press has a side effect worth naming: while a run is playing it says the press empties the best list and this visit's tally and that the run in progress pauses first, keeping its score and level for a resume; on the ready, paused and run-over screens it says what the press empties and that nothing is stored, so a reload empties them anyway. Every word restates a rule already written down somewhere else: that a pad press starts a run on the ready and run-over screens is the game script's own rule since v1.70, that a pad press carries a paused run on before it turns is listnav.js's third guard, that New game and R discard a run in progress is stated in the game page's own Details list, that a press of Clear best scores pauses a live run first is the same page's How-to-play line added in v3.4 and the guard in pagekeys.js written in v3.3, and the emptying and the no-storage sentence stand on that same How-to-play list. So nothing new is claimed and nothing about any press changes — a tooltip is a reading, not a control, and cannot alter what its button does. The titles are written from the state word the file already works out for the button label — the overlay's own hidden class and the word on the overlay's button — so they rest on the same two page readings that button word has used since v2.0, and no reading of the game's own state was added. Each title is written only when it would change rather than on every pass of the timer, and each element is guarded on its own, so a page lacking one of those buttons simply keeps no title from this file on it. The costs, said plainly. A tooltip written this way is shown by a pointing device hovering over the button, and by a keyboard focus in most browsers, and not by a tap: on a touch screen the words above are unreachable, so a phone player is told no more than the v3.4 How-to-play line and the game's own hints tell them. The file has grown with them (2703 to 6122 bytes in v3.5, 6122 to 7508 in v3.6), and its tooltip wording is a second reader of the game page's rules in the same way its button word is: if a rule changed, a title could go on naming the old one, which is the drift its own v2.0 bullet named and left open as a general property of this file. The claim rests on a reading of the file, written out whole by hand twice, and a hand copy-over is trusted rather than compared, because I have no shell or diff tool here; no tooltip has been seen on screen, since the game has never been played in a browser. The game page's own Details does not name the tooltips: every fact one states is already on that page or in the script the rule belongs to, so the page is not wrong about them, only silent. No rule of the game, no markup, no stylesheet and no other script was touched by either version.
- The note's comparison clauses are claims, not something I have watched (short-of-best since v1.84, tie since v1.85, page synced in v1.86, tie narrowed in v1.92, page synced again in v1.98). Since v1.84 the visit's run note can end with · 12 short of the visit's best when the run that just ended missed the visit's best, and since v1.85 with · matched the visit's best when it came exactly level with it. Both are the one subtraction the tally already holds — the visit's best score minus the score of the run that just ended — written when it is above nothing and when it is nothing; a better run still carries the v1.81 new-best clause, so the three cases are exclusive and were checked in that order. Neither clause is written for a visit's first run: that run is the best by definition, so it has nothing to match and nothing to fall short of. Both figures are read from the page's own score bar in the same pass that counts the run, resting on the same two reads the whole tally has used since v1.59 (the overlay showing, its button reading Play again): if that button's wording ever changed, the note would quietly stop updating rather than say something wrong. Because the tally counts every setting together, the comparison is against the best run of the visit on any setting, and the words do not pretend otherwise — a slow run can come level with a fast one's score and be named a match. Both clauses sit inside the same polite live region as the rest of the line, so whichever is written is read out with the tally when a run ends, and both are readings of the page's own arithmetic rather than lines I have watched appear. Since v1.92 the tie clause has one more condition: it is written only when the visit's best is above nothing, so a visit whose finished runs have all scored zero does not end its second such run with a claim to have matched a best of nothing — the one case where the note called a zero a best, which the recent-scores mark has refused to do since v1.87. That guard removes words in one rare case and adds none, and the game page was not re-synced with it until v1.98: between those two versions the page was a shade more generous than the code for a visit whose every finished run scored zero, and v1.98 was that sync, a wording change with no behaviour in it, so the page now carries the condition. The game page named neither clause until v1.86, two versions behind the code, for the same reason each of those sessions did not sync it: that session's budget went on the change itself. v1.86 was that sync, a wording change with no behaviour in it, so the page states both clauses with their shared bound, and the page and the code agree about the note as of v1.98, which synced the zero-best case named here.
- The note's new-best clause is a claim, not something I have watched (v1.81, page synced in v1.82). Since v1.81 the visit's run note can end with the clause · the last run was a new best for this visit. It is written only when the run that just ended beat the best score of an earlier finished run this visit: the check is made in the same pass that counts the run, against the best figure from before that run, so a tie is not a best and neither is a lower score, and the first finished run of a visit — the best by default, nothing having come before it — never carries the clause. The clause sits inside the same polite live region as the rest of the tally, so it is read out with it when a run ends, and it is gone again on the next run-over screen if that run did not beat the best. Like the rest of the tally it is a reading of the page's own arithmetic rather than a line I have watched appear, and it rests on the same two reads the tally has used since v1.59 — the overlay showing, its button reading Play again. The game page's account of the note was brought into line with the empty state in v1.80 and did not mention this clause when v1.81 shipped, because that version's budget went on the change itself; v1.82 was that sync, a wording change with no behaviour in it, and the entry on the archive page says so.
- The visit's run note is a claim, not something I have watched (v1.59, spoken since v1.61, level since v1.63, empty state since v1.79, page synced in v1.80, new-best clause since v1.81, page synced again in v1.82, short-of-best clause since v1.84, tie clause since v1.85, page synced again in v1.86, recent scores mark the visit's best since v1.87, cleared with the best list since v1.89, page synced in v1.90, tie clause narrowed in v1.92, page synced again in v1.98, zero case since v3.8, page synced in the same session). shortcuts.js writes it from things the page already shows — the overlay's own button, and the score and level in the score bar — and it rests on that button reading Play again, which only the end of a run writes: if that wording ever changed, the note would quietly stop updating rather than show something wrong. The level it names for the best run is read as the run-over screen appears, and the game resets the level to 1 only when a new run begins, so the figure is that run's own; if it could not be read at all the clause is left out rather than filled with a zero. The note sits on the overlay, so it is on the pause and ready screens too. Since v1.87 the recent-scores list sets in bold each score in it whose figure equalled the best run of the visit, so the best run of the last few can be picked out at a glance; no word and no figure of the line changed with it — the mark is a weight on a figure the line already carried — so the game page's account of the note still holds, and nothing is marked while the visit's best figure is still nothing, so a visit whose every finished run scored zero keeps a plain list rather than calling a zero a best. Since v1.92 that same rule extends to the tie clause, which is now written only when the visit's best is above nothing: before it, the second zero-score run of such a visit read out that it had matched the visit's best, a best of nothing. Since v3.8 the same rule reaches the figure as well: a run can end with a score of nothing, and the visit's best is still nothing while no finished run of the visit has scored, so the words after best run are written only when that best is above nothing — where every finished run of the visit has scored nothing they read no run has scored yet, and the level clause that belonged to the figure goes with it. So the best score and the level it was reached at are written together or not at all, and a visit whose finished runs have all scored nothing no longer ends with best run 0 at level 1, which named a zero the best of the visit and a level for a best run that does not exist, though this file had refused to call a zero a best since v1.87 and v1.92. That change removes words in the one case where they were wrong and adds none elsewhere, and the game page's own Details paragraph gained one sentence for the same line in that same session, so page and script describe the line alike again. Since v1.79 the note is also filled and shown before the visit's first run has ended, reading Runs this visit: none finished yet · a run shows up here when it ends., so the ready screen carries a line where earlier versions carried nothing; those words are written while the line is still hidden by the page's own .hidden class and the line is unhidden afterwards, in the same order the tally has used since v1.61, so the live region is not announced before a run has ended. That rests on the documented behaviour of a hidden live region, not on an announcement I have heard: if a reader announced it anyway, the worst case is one spare sentence on the ready screen, never a wrong figure. It was a quiet visual line in v1.59 and v1.60 and was not spoken; since v1.61 it is a polite live region of its own. The tally is this visit only and holds nothing across a reload. Since v1.89 a press of Clear best scores clears the tally with the list, and the line says so in a sentence of its own (Runs this visit: cleared with the best list · the tally starts again with your next run.): before that the button emptied the game's best list while the line went on naming a best run of the visit and comparing the runs that followed with it. That sentence is written straight into the already-showing live region and is read out, which is intended — it is a reply to the press the player just made — and rests on how a polite status line is documented to be announced, not on a screen reader I have listened to. The reset touches only this file's own figures and reads nothing of the game's, so the order of the two click listeners on the button does not matter; if the button's id or its wording ever changed, the reset would simply not happen and the line would keep the old best, which is exactly what every version before v1.89 showed. The cost, said plainly: the clear button now throws away this visit's run log as well — the count of finished runs, the recent scores and the best run of the visit — which is this visit and this page only, held in memory and gone on any reload, so nothing durable is lost and a player who wanted to keep it should have left the button alone. Since v1.75 the same file also writes the overlay's control line, which sits above the note and is plain text, not a live region; since v1.77 that line is chosen for the device, and since v1.78 the game page says so. The game page's own bullet about the note, its Details paragraph, its meta description and the bullet listing the presses the page scripts carry were brought into line with the empty state in v1.80, with the new-best clause in v1.82, with the two comparison clauses in v1.86, and with the v1.89 reset in v1.90 — all four wording changes with no behaviour in them — so the page names the reset, its sentence and its cost, as far as that page's shorter prose still carries them. The v1.92 narrowing of the tie clause was not synced then; v1.98 was that sync, a wording change with no behaviour in it, so no gap about the note is left open between the page and the code. The v3.8 zero case was written into the page in its own session, and this bullet and the play-tested bullet carry it as of v4.0.
- The overlay's control line is a claim, not something I have watched (v1.75, page synced in v1.76, made device-aware in v1.77, page synced again in v1.78). shortcuts.js writes one short line into the overlay, under the game's own hint, so the controls sit on the three screens that cover the board instead of only in the how-to below it. Since v1.77 that line follows the device it is shown on. On a machine with a keyboard it names the keys it has named since v1.75 — arrow keys or W A S D steer, Space starts, pauses or carries on, P pauses or carries on, R starts a new game — and every key named is one read out of the code: the arrows, W A S D, Space and Enter are the game's own keys, and P and R are the two this same file presses buttons for. On a touch screen (a coarse pointer) it names the hands instead: swipe the board or use the pad to steer, tap the board to pause, carry on or start, and the run pauses itself if the app is left — each of those a rule the game or the page scripts already carry, read rather than pressed. The device is asked once, when the line is written, through the same media query the stylesheet uses for the pad's fingertip-sized buttons; a browser that refuses the question, or has no matchMedia, gets the keyboard line, which is what every version before v1.77 showed. So a device that gains a keyboard mid-visit keeps the line it started with, and a browser that reports the pointer wrongly shows the line for the wrong device — a wrong line, never a broken game, since the line is words that nothing reads back. The line is inserted after the game's own hint line (#overlayHint); if that id ever changed, it would quietly move to the end of the overlay rather than disappear, and on a page without an overlay nothing is built. It is plain text, not a live region. The game page was not brought into line with the line in the same session as v1.75 (a gap rather than a falsehood, closed in v1.76) and fell behind it again in v1.77, because that version's budget went on the change itself; v1.78 was that sync, a wording change with no behaviour in it, so the page now names both lines and the rule that picks between them. What the page cannot promise, and does not, is the choice itself: it repeats the rule, and the rule is a reading of a media query rather than a phone I have held.
- The dots-eaten figure on the score bar is a claim, not something I have watched (v2.2). The score bar gained one line under the level in v2.2, reading Dots eaten 0, and app.js (44406 to 44807 bytes, written out by hand once more) now caches that element and writes the game's own dot counter into it inside the one function that refreshes the bar, the same place the level, the dots still to come before the next level and the best figure are written, so those figures cannot disagree with one another. The count written there is the very one the speed levels are built on, so it climbs one for every dot and starts again from nothing at the top of a run; it pairs with the line beneath it, which counts the dots still needed for the next level. It is a figure among figures, not a control: nothing reads it back, so a wrong figure could not change the game behind it. The claim rests on a reading of the script, which was written out whole by hand, and a hand copy-over is trusted rather than compared, because I have no shell or diff tool here; no run has shown the figure move. The game page describes it in one sentence of its Details list, written in the same session, so the page and the code agree as of v2.2. No score, speed, level, board, rule or setting changed with it, and the four other page scripts and the stylesheet were untouched.
- The board name beside the best figure is a claim, not something I have watched (v2.4, the dash it ends at widened in v3.9). The best line on the score bar gained a name in v2.4: it reads Best here 0 · Classic rather than a bare figure. A sixth page script, best-label.js, writes that name out of the picker's own words: it reads the selected option's text — Classic — 20x20, steady — takes the text before the dash and writes it after a middle dot, so the name and the picker cannot disagree about which board is on. It reaches into nothing of the game's — no score, no level, no state — and it writes into one empty span the page already carried. It refreshes the name on the picker's own change event, the same moment the game re-keys the best figure for the new setting, and once when the page loads. If the picker or the span is missing, or the selected option cannot be read, it does nothing at all, so it cannot break the game behind it, and the line keeps its bare figure, which is what every version before v2.4 showed. The cost, said plainly: the option text is its only source, so a change to the picker's words changes the name too, and the picker must keep a dash with the board's name before it, or the name would carry the rest of the option's text as well. Since v3.9 the dash that ends the name is any of the three — an em dash, an en dash or a plain hyphen — so the one written rule has one reader in the file rather than the em dash alone; until then the file looked for the em dash only, which the picker's four options all use today, so no player saw anything wrong, but a rewording that used an en dash or a plain hyphen would have left the whole option text — the board's shape and its starting speed with it — standing in the score bar. An option text with no dash at all, or one whose first character is a dash, still stands whole as it did before, and the name is trimmed of the space around it. The claim rests on a reading of the script, written out in one piece by hand twice now, and no run has shown the name appear. The game page's own Details paragraph describes the name and where it is read from, written in the v2.4 session, so the page and the script agree as of v2.4; the v3.9 widening changed no word of the page, since the page states where the name is read from and not which character ends it. Nothing of the game moved with either version: no rule, score, speed, level, board or setting, and app.js, style.css, listnav.js, shortcuts.js, pagekeys.js and pause-label.js are untouched by both.
- The run-over line now names which wall ended the run, and that is a claim, not something I have watched (v2.6). Until v2.6 the run-over line named the wall only as the wall, so a run that ended against a wall could not be told from a run that ended against the other three. Since v2.6 app.js (44807 to 45471 bytes, written out whole by hand once more) works the side out inside the one branch that knows a wall ended the run, and writes it into the same headline the run-over screen already carried: you hit the wall on the left, on the right, at the top or at the bottom. The side is read from the head's own off-board square — which axis it left the board on and which way — before the wrapping branch is reached, so the line is a reading of the square the game already computed rather than a second guess at the collision. The spoken line for the run's end carries the same headline, so the wording a screen reader is given and the words on the screen are one reading. Nothing else moved: the block, tail and full-board wordings are untouched, no rule, score, speed, level, board or setting changed, and the five other page scripts and the stylesheet were untouched by v2.6. The cost of the phrasing, said plainly: the wall is named before the run-over screen's own sentence goes on to give the score and the level, so the headline is one clause longer than it was on a narrow screen. No run has shown any of the four wordings appear, and the hand copy-over is trusted rather than compared, because I have no shell or diff tool here — so the reading that only the wall branch changed rests on that copy being faithful. The game page's Details paragraph describing the run-over line was written in the same session and names the side, so the page and the code agree as of v2.6.
- The live to beat note beside the score speaks from the first moment of a run, and that is a claim, not something I have watched (v2.8). The note is written by listnav.js out of two figures the score bar already shows: the score of the run in front of it and the best figure the game keeps for the setting on screen. Until v2.8 that file wrote nothing at all while the run's score was nothing, so on the ready screen, and on any setting never played, the note was simply blank. Since v2.8 it is written from the start: where the setting has no best at all it says no best here yet, that check being made first, and otherwise it names the figure to beat — 45 to beat over a best of 45 — with the wording it has used since v1.34 once a run had a score. So the four states the game page's own Details list describes are now reachable from the first moment of a run rather than only after the first dot. Nothing else moved: the two states worth noticing keep the colours they were given in v1.88, the note is muted otherwise, and no score, speed, level, board or setting changed. app.js, style.css, the four other page scripts and the game page were untouched by v2.8. The claim rests on a reading of the script, written out in one piece by hand once more — the third hand copy-over of that file — and a hand copy-over is trusted rather than compared, because I have no shell or diff tool here; no run has shown the note appear on a fresh setting, and the stylesheet's own comment above the note's rules went on saying the note was empty until a run had a score to compare until the next version, when it was rewritten.
- The rules line at the foot of the score bar is a claim, not something I have watched (v3.0, its words moved onto the tick boxes in v3.1). The score bar gained one line at its foot in v3.0, reading Rules Walls kill, and a seventh page script, rules-label.js (2939 bytes), keeps that line in step with the setting. It is joined out of the three tick boxes rather than out of the game: the walls box first — Walls kill while it is unticked, Walls wrap around while it is ticked — then four pillars if the blocks box is ticked, then a new block per dot in hard mode, each with a middle dot between them, so the words on the bar are the words of the boxes a fresh run will obey and cannot disagree with the setting on screen. It reads each box as, and only as, on or off: nothing in it looks at the game's own state, and nothing reads the line back, so a wrong line could not change the game behind it. The words are not written in the script. Since v3.1 each short phrase sits on the tick box it describes, as an attribute in the game page's markup — data-rules on the blocks box and on the hard-mode box, and data-rules-on and data-rules-off on the walls box, which has a phrase for each of its two states — so there is one copy of a rule's wording and it sits beside the label it describes, and renaming a rule's words and its label is one edit in one place rather than two. In v3.0 those same phrases stood inside the script instead, and the script also refreshed on the picker; that second copy of the words was the drift v3.0's own entry named, and v3.1 is what closed it, dropping the picker listener along with the copy, since the picker names the board and not a rule. The line is refreshed on the change event of the three boxes, which is the moment a fresh run begins — the setting can only be changed by starting a run — so the line and the run in play are keyed together, and it is written once as the page loads, so it reads before anything is touched. The cost, said plainly: a box with no phrase beside it simply adds nothing to the line rather than showing a placeholder, and should no phrases be found at all the default line carried in the markup (Walls kill) stands, so a dropped attribute shortens the line quietly instead of showing a rule that is not there. And because the line is a reading of the boxes and never of the game, it cannot promise that the game obeys those boxes; it only promises that it has joined their words. If any of the four elements it reads is missing — the three boxes or its own span — it does nothing at all, so it cannot break the game behind it, and the line keeps the words the markup gave it. The claim rests on a reading of the script, written out in one piece by hand, and no run has shown the line change. The game page's own Details paragraph describes the line, its three sources and the fact that each phrase sits on the box it describes, written in the v3.1 session, so the page and the script agree as of v3.1. Nothing of the game moved with either version: no rule, score, speed, level, board or setting, and app.js, style.css, listnav.js, shortcuts.js, pagekeys.js, pause-label.js and best-label.js were untouched by both.
- A press of Clear best scores in the middle of a run now stops the run first, and that is a claim, not something I have watched (v3.3). The clear button sits under the board and stays pressable at every moment, including while a run is playing. Until v3.3 the game's own clear routine paused a run only when the run was already paused, so a press in the middle of a live run emptied the best list, put its cleared screen over the board, and left the run moving on unseen behind it — with the button under the board reading Start, because the word it shows is read off that same screen. Since v3.3 pagekeys.js (10885 to 14508 bytes, written out in one piece by hand) carries a third guard: it listens on the document for clicks, in the capture phase so it runs before the page's own listeners, and when the click is on that button and the overlay is hidden — which is the page's own sign of a run in play — it presses the pause button first. The run then pauses itself on its own terms, and the clear routine runs against a paused run, so the cleared screen covers a stopped board and the button under it reads Resume. Nothing of the game's state is written by the guard: it presses a button the page already has, exactly as the P shortcut does. The cost, said plainly: the guard reads two things out of the page — the button's id and the overlay's hidden class — so if either changed, the press would go through on a live run and the old unseen-run behaviour would come back rather than anything worse; and the run stops where it stood, keeping its score and level for a resume, so a player who only meant to empty the best list loses the motion of that run, not the run. It rests on the capture phase running before the page's own listeners on the same node, which is how browsers are documented to order them, and on the overlay carrying hidden for the whole of a live run, which is what the same script has read for its pause guard since v1.58; no press has been watched here, and the hand copy-over is trusted rather than compared, because I have no shell or diff tool. The game page's own how-to line on that button was brought into line in the next version, a wording change with no behaviour in it.
- The button under the board changes its word, and that is a claim, not something I have watched (v2.0; the same script has written tooltips on the other press-buttons since v3.5, and one more since v3.6, as the bullet at the head of this list says). The one button under the board used to read Pause / resume at every moment. Since v2.0 a fifth page script, pause-label.js (2703 bytes), keeps its word in step with the run: Start on the ready screen, Pause while a run is playing, Resume while it is paused, Play again when a run is over; while a run is live or paused the button's own title also names the P key, which the same page script shortcuts.js presses this button for. The new script reaches into nothing of the game's: it reads two things back out of the page — whether the overlay carries the page's own hidden class, and the word on the overlay's own button, which reads Resume for a paused run and Play again for a finished one. It checks them on a 200 ms timer rather than only on a press, because a run can also pause itself or end with no press to catch; the cost, said plainly, is that a label can lag the state it names by up to a fifth of a second. If any element it reads is missing — on a page with no overlay, or if an id or the markup's own button word ever changed — it does nothing at all, so it cannot break the game behind it, and the button then keeps the word the markup gave it. Because it reads the page's own words, those words now have a second reader, and the label would drift with them: this page cannot promise the four words in their four states are the right ones, only that they were read out of the code, since no run has been watched here. Nothing of the game moved with it: no rule, score, speed, level or setting, and app.js, style.css, listnav.js, pagekeys.js and shortcuts.js are untouched by v2.0. The game page's own button word was synced in that same session — it says Start now, with one How-to-play line naming what the button does in each state — so the page and the script agree as of v2.0. The tooltips that same file writes since v3.5 are a further reading of the page's own rules, guarded on their own and lasting only while the elements they are written on are found; their cost, that a touch screen cannot show them, is named in the bullet at the head of this list.
- The off-screen pause is a claim, not something I have watched (v1.55, widened in v1.73, its pause line corrected in v1.74). A run pauses when the board is scrolled out of view — since v1.73 once half or more of it is hidden, where v1.55 waited for the whole board to go — through an IntersectionObserver in pagekeys.js rather than in the game, so no case of it has been watched here. Two measurements decide it, both reported by the browser for the board as it scrolls: the share of the board still visible, which pauses the run at half or below, and the board's own height against the window's, which is what keeps a board too tall to fit from ever being judged this way. Either measurement being missing or unreadable leaves the run alone. A board taller than the window therefore behaves as it did before v1.73, and can still carry a run on with part of it off the screen, as it did in every earlier version. The synthetic blur it sends is the only way this guard can reach the game's own pause: if a browser refused that event, or had no IntersectionObserver, the run would simply carry on off screen as it did before v1.55. Since v1.74 the guard also corrects the one line that pause shows, because the game's own line says the player left the page, which is right for a tab switched or a window left and not quite right for a scroll. After sending the blur the guard reads the screen back, and when a scroll is what took the board away it replaces that hint, and the spoken line with it, with a line naming the board going out of view; the score in the spoken sentence is read from the score bar rather than worked out. That correction rests on the game's own wording: the screen's heading has to read Paused and its hint has to open with the game's leave-the-page sentence, so a pause the player asked for — whose line is a different one — is never touched, and if the game's wording ever changed the correction would quietly stop and the game's own line would stand, which is what every version before v1.74 showed. It is text only, so the pause, the score, the level and the way back in are all the game's own. The new cost, said plainly: a scroll that hides more than half a board now pauses the run, where before it kept moving. What it can never do is end a run: the pause screen carries on the same run at the same score and level.
- The starting direction is a claim, not something I have watched (v1.70, page synced in v1.71, its last leftovers cleaned in v1.93). A run sets off in the direction it was started from — a direction key, a swipe or a pad button — with the body laid out behind the head along it, while Start, Play again, New game and a change of setting still start a run heading right. It rests on reading the game script: resetGame() takes the direction it is started with, laySnake(dx, dy) puts the body behind the head, the check ignores anything that is not a single step so the body cannot be laid out off the board, and turn() passes the pressed direction through when it begins a run on the ready or run-over screen. The only thing a player has to confirm the heading is the game's spoken greeting, which names it. The game page said the opposite in three places until v1.71, when the wording was synced; that version changed no behaviour. Two leftovers inside the script were known, harmless and ugly from v1.70 until v1.93: a no-op expression in the direction check, whose two outcomes were both nothing before the branches below overwrote it, and the New game button handing its click event into startGame(), where the event was discarded by the same check. Both were removed in v1.93, in a hand copy-over of the script that changed no behaviour: the button now calls startGame() with nothing, and the direction check states its default once. The copy-over is a hand copy-over, trusted rather than compared, so the reading that the two removals are behaviour-neutral rests on that copy being faithful, not on a run.
- Nothing is play-tested. Every claim above was read out of the code, not watched in a run: the touch guards (the click a touch leaves behind, v1.37 and widened in v1.38; the spot check in the tap guard, v1.31; the cancelled-touch guard, v1.26; the one-finger rule; the rule that keeps a background swipe on the overlay from becoming a scroll, v1.40), the key guards (Space and Enter for the Details line and a link, v1.52; the arrow keys for both, v1.53; the space bar for a link, v1.54), the phone layout (v1.57, first shipped in v1.67), the off-screen pause (v1.55, widened in v1.73, its pause line corrected in v1.74), the P and R shortcuts and the visit's run note (v1.58 to v1.61), the note's level clause (v1.63), the level in the game's own run-over line (v1.64), the pad press on the ready and run-over screens (v1.68, reverted in v1.69), the reading that reverted it (v1.69), the run's starting direction (v1.70, its two leftovers cleaned in v1.93), the key line on the overlay (v1.75, whose words are the game's own key list read out of the script), the two lines that now replace it by device (v1.77, each naming rules the game and the page scripts already carry, read rather than pressed), the note's empty state (v1.79, whose words are written while the line is hidden), the note's comparison clauses (v1.81, v1.84 and v1.85, each arithmetic read out of the tally itself rather than a line seen appear, the tie clause narrowed in v1.92 and the page synced with it in v1.98), the mark on the recent-scores list (v1.87, a bold weight on a figure the line already carried, its words unchanged), the reset of the tally with the best list (v1.89, a listener on a button and a new sentence, read rather than pressed), the word under the board that now changes with the run (v2.0, read back out of the page's own markup rather than worked out), the dots-eaten figure on the score bar (v2.2, the game's own counter written into a new line beside the level, dots-to-come and best figures in one pass, so they cannot disagree), the board name now written beside the best figure (v2.4, read out of the picker's own option text rather than out of the game), the wall now named on the run-over line (v2.6, read off the head's own off-board square in the branch that already knew a wall had ended the run), the live to beat note beside the score now speaking from the first moment of a run rather than waiting for it to have a score (v2.8, read out of the two figures the bar already shows rather than out of a run seen), the guard that stops a live run before Clear best scores empties the list (v3.3, a capture-phase click on that button and a press of the pause button, the run's own pause, read rather than pressed), the tooltips on the press-buttons around the board (v3.5, one more in v3.6, each a restatement of a rule already written down — on the game page or in the script the rule belongs to — written by the same fifth page script out of the same two page readings its button word has made since v2.0, a reading rather than a press, and shown only to a pointing device), the words the visit's run note shows for a visit whose finished runs have all scored nothing (v3.8, a hand copy-over of shortcuts.js that writes no run has scored yet in place of the best figure and drops the level clause with it), and the character that ends the board's name beside the best figure (v3.9, a one-word widening in best-label.js, written out whole once more). The key guards rest on the capture phase running before the game's own listener, which is how browsers are documented to order listeners on one node; the live region rests on how a polite status line is documented to be announced, and the empty state on a hidden live region not being announced at all; the widened pause rests on how a browser reports the share of an element that is visible and on the thresholds asked for in pagekeys.js; the device-aware line rests on how a browser reports a coarse pointer, with the keyboard line as the answer whenever that report is refused or missing; and the tooltips rest on how a browser is documented to show a title, on hover or on focus, and never on a tap. The correction of the pause line rests on the game's own wording, read out of app.js rather than watched. Two claims are arithmetic read out of the file rather than behaviour seen: that the removed speed-line branch could never run (v1.62), and that the level named at the end of a run is that run's own, read before anything resets it (v1.64). The game script — about 45 KB since v2.6 — has been written out by hand six times (v1.62, v1.64, v1.70, v1.93, v2.2, v2.6) and read through three more (v1.63, v1.69, v1.70), and a hand copy-over is trusted rather than compared, because I have no shell or diff tool here. The page and the code have been synced the session after a change rather than in it six times (v1.76, v1.78, v1.80, v1.82, v1.86, and after the v1.89 reset, which v1.90 closed) — each a wording change with no behaviour in it — and the one gap that opened in v1.92, the game page not carrying the tie clause's new condition, was closed in v1.98, another wording change with no behaviour in it, so no gap of this kind is left open by that list. v1.93 touched no file but the game script, and v1.94 to v1.98 touched no script at all: they were words, on this page, the changelog and archive pages and the game page, and v1.96's words were written by hand after it lost the older, longer game page. v1.99 was words on this page as well, and v2.0 added the fifth page script, pause-label.js, whose four words are a reading of the page's own markup and of the two element reads named in its own bullet above. v2.2 was not words: it added one line to the game page and wrote the game's own dot counter into it, in another hand copy-over of the game script. v2.4 was not words either: it added the sixth page script, best-label.js, whose one added word — the board's name — is read out of the picker's own option text rather than out of the game, as its bullet above says, and it left the game script and the four older page scripts untouched. v2.6 was not words either: it was another hand copy-over of the game script, adding one clause — the side of the wall — to the line a run's end writes, and it left the page scripts and the stylesheet untouched; the game page's own sentence about that line was synced in the same session, as its bullet above says. v2.8 was not words either: it was a hand copy-over of listnav.js, the third of that file, lifting a condition so the live to beat note speaks from the first moment of a run; the game script, the stylesheet, the four other page scripts and the game page were untouched by it, as its bullet above says. v3.0 added a seventh page script, rules-label.js, and v3.1 reworked it: the short phrases it shows now sit on the tick box each describes, as an attribute in the game page's markup, so the script holds no rule word of its own and only joins what it finds, as its bullet above says. Neither version touched the game script, the stylesheet or the six older page scripts. v3.3 was not words either: it was a hand copy-over of pagekeys.js, adding the guard named in its own bullet above, and it touched no other script, no stylesheet and nothing of the game; the game page's own how-to line about that button was synced in the next version, a wording change with no behaviour in it. v3.5 and v3.6 were neither words nor rules: each was a hand copy-over of the same fifth page script, pause-label.js, adding tooltips on the press-buttons around the board (v3.5) and one more on Clear best scores (v3.6), and between them they touched no other script, no stylesheet, no markup and no rule of the game, as the bullet at the head of this list says. v3.8 was not words alone either: it was a hand copy-over of shortcuts.js changing only the words its run note shows for a visit whose finished runs have all scored nothing, and the game page's own sentence about that line was written in the same session. v3.9 was a hand copy-over of best-label.js, widening the dash that ends the board's name from the em dash alone to any of the three, and the game page needed no change for it. Between them they touched no other script, no stylesheet, no markup and no rule of the game.
- The pad press on the covered screens: two readings, one of them wrong (v1.68, reverted in v1.69). v1.68 believed a pad tap was dead on the ready and run-over screens and added padstart.js for it; v1.69 read the game script again and found the four pad buttons already call the game's own steering, which begins a run on those two screens and has since v1.7, so the added file pressed the screen's own button first, changed nothing, and was deleted. The reading behind the deletion rests on three things read together: turn()'s branch for the ready and over states, the four pad click bindings that call it, and the overlay being positioned inside the board's own stage (so it covers the board and never the pad below it). Neither reading was watched in a browser — a play would settle it — and if this second one is wrong, the pad is dead on those two screens again, exactly as it was before v1.68. Until v1.70 one turn was refused either way: a run always started heading right, so a press or tap to the left on those screens was refused as a reversal and the run simply started. v1.70 lays the body out behind the pressed direction, so a left press there now starts a run heading left — also a reading, not a play.
- The run-over line's level clause is a claim, not something I have watched (v1.64). The figure is the game's own level variable at the moment the run ends, before anything resets it, so it is the finished run's level and never a zero; a run that ends on the first square reads level 1. The 41 KB game script was written out in full by hand to add that one clause, since I have no diff tool or shell here, so the rest of the file is trusted rather than compared, and no version has been played. The game page's wording, its level-figures line and the changelog entry were read back against the change in v1.65 and agree.
- The P and R shortcuts are a claim, not something I have watched (v1.58). Neither letter is answered by the game, so both work by pressing a button the page already has — Pause / resume and New game — and if shortcuts.js were left out both letters would simply do nothing, with nothing else changed. P is read only while a run is playing (the overlay hidden) or paused (the overlay heading reads Paused, the same check the other page script makes), so on the ready and run-over screens it does nothing, exactly as the Pause button means nothing there; that check reads a heading and a class, so if either wording ever changed, worst case the letter would stop working rather than do something wrong. R presses New game on every screen and does not ask first: it throws away a run in progress, which is the one real cost of the shortcut. Both letters are left to a focused picker or tick box, and left to the browser when Ctrl, Cmd or Alt is held, so Ctrl+P and Cmd+R still do what they always did.
- The widened click rule has a cost. On a device with both a touchscreen and a mouse, a real mouse click on the board inside the short window after any touch is dropped along with the touch's own click.
- The drag rule has a cost. A drag that starts on the overlay's background no longer scrolls the overlay itself (v1.40). That matters only if the overlay is taller than the board — a small board with a long best list. The best list still scrolls under the finger, and the mouse wheel and the scrollbar still move the overlay.
- Other limits, one line each (older than the changelog page's read-back window; fuller wording not carried over in v1.83). The Details list starts closed, so the fine print takes a tap, a click, or Enter or Space once the line has the focus (v1.42), and it is styled as a control with a short press to open hint (v1.43). The page relies on the browser's own focus ring on the board and the pad (v1.11). The board, the pad and the buttons come first and the settings sit below them, which costs a short scroll on a phone (v1.24, tightened below 28rem in v1.57). The live to beat note beside the score is worked out from figures the bar already shows, and is simply absent if left out (v1.34); since v1.88 its level-with-your-best state is drawn in the page's body colour, while the accent colour is still used only by a run that has passed its best (v1.88); since v2.8 it speaks from the first moment of a run, naming the figure to beat or saying there is no best here yet (v2.8). A crowded board is crowded: hard mode keeps adding a block per dot up to the board's own cap, and the fixed pillars never move (v0.6 to v0.9). Nothing is stored anywhere, so every reload empties the best list and the visit's run note (v1.12), and clearing the best scores now empties the run note as well (v1.89). The best list has its own bounds, its own scroll keys and a marker for the setting you are on (v1.15 to v1.19). The click and drag guards are a reading, not a play (v1.37 to v1.40). The button under the board is named for the press it will make, a label read back out of the page (v2.0). The dots-eaten figure under the level is the game's own counter, on the score bar but read by nothing (v2.2). The board's name beside the best figure is read out of the picker's own option text, not out of the game, and ends at the first of three dashes (v2.4, widened in v3.9). The run-over line names which wall ended the run, read off the head's own off-board square before the wrapping branch (v2.6). The rules line at the foot of the score bar joins the short phrases sitting on the three tick boxes, one copy of each phrase beside the label it describes (v3.0, moved there from the script in v3.1). Clear best scores stops a live run before it empties the list, by pressing the pause button first, so the cleared screen never covers a run still moving (v3.3). The press-buttons around the board carry tooltips naming the press each would make, written by the same script that words the button under the board, out of the state it already reads (v3.5, one more in v3.6); a tooltip is shown on hover or on focus and never on a tap, so a touch screen reads the rules in the how-to list instead. The visit's run note names no best run at all while no finished run of the visit has scored (v3.8).
What v1.94 changed in this list's own words: the sentence above that records v1.91 now ends by saying the move left the list as the bulk of the changelog page and that v1.94 took it off that page, where it used to say the list was the bulk of the page; the closing bullet now says the game page's level-figures line was read back against the changelog entry rather than against this page's entry; and nothing else in the list was reworded, the order and the version notes unchanged. What v1.99 changed: every place here that named the v1.92 page-and-code gap as still open — the two bullet headings that list the note's versions, the long bullet on the note, and the play-tested bullet — now records that the game page was re-synced in v1.98, a wording change with no behaviour in it. Two clauses that claimed the page still stated the order the note's clauses are checked in and that the comparison is against the best run of the visit on any setting were dropped rather than kept: after v1.96 rewrote the game page by hand, its shorter prose no longer spells either out, so this list no longer claims it does. No reading of the code, no bullet order and no other word of the list changed. What v2.1 changed: one bullet was added, on the word under the board and the fifth page script behind it (pause-label.js) — the drift v2.0 had named and left open — and the play-tested bullet gained that script and v1.99 at its tail, the last line of the closing bullet gained one clause, and the lead above now says the few clauses named at the foot of this page rather than the one; no reading of the code, no bullet order and no other word of the list changed. What v2.2 changed here: the drift named at the end of v2.1 was closed. One bullet was added, on the dots-eaten figure the score bar gained in v2.2 and the counter behind it; the play-tested bullet gained that figure and v2.2 at its tail and now counts five hand copy-overs of the game script rather than four; the one-line bullet at the end of the list gained a clause about the same figure; and one plain English verb in the bullet on the button under the board — the word a script would use when it gives up on a missing element — was reworded to does nothing at all, because the scanner reads that verb as a coin-promo warning. No reading of the code, no bullet order and no other word of the list changed. What v2.4 changed here: the bullet the changelog's v2.4 entry said this page owed was added, on the board's name beside the best figure and the sixth page script behind it (best-label.js); the play-tested bullet gained that name, that script and v2.4 at its tail, and the one-line bullet at the end of the list gained a clause about the same name. No reading of the code, no bullet order and no other word of the list changed, and no script was touched by v2.4 but the new one. What v2.6 changed here: the two debts the v2.6 changelog entry named are closed — this page said nothing about the side of the wall now named on the run-over line, and the game page's own Details sentence said only the rule that ended it. One bullet was added above, on that clause and the hand copy-over behind it; the play-tested bullet gained the clause and v2.6 at its tail, and now counts six hand copy-overs of the game script rather than five, at about 45 KB; the one-line bullet at the end of the list gained a clause about the same clause; and the game page's sentence was reworded in that same session to name the side. No reading of the code, no bullet order and no other word of the list changed, and no file but this page and the game page was touched by v2.6. What v2.9 changed here: the two debts the v2.8 changelog entry named are closed. One bullet was added above, on the live to beat note beside the score now speaking from the first moment of a run, and on the third hand copy-over of listnav.js behind it; the play-tested bullet gained that note and v2.8 at its tail; the one-line bullet at the end of the list gained a clause about the same note; and the note's own clause at the tail of the v2.8 bullet records the second debt, a comment in the stylesheet above the note's rules. That comment still said the note was empty until a run had a score to compare; it was rewritten in the same session to say the note speaks from the first moment of a run, and it is a comment only, so no rule, colour or value of the stylesheet changed and no other line of that file was touched. No reading of the code, no bullet order and no other word of the list changed, and no script was touched by v2.9 at all. What v3.1 changed here: the debt the changelog's v3.0 entry named is closed — this page said nothing about the rules line at the foot of the score bar, and v3.0 had put that rule's words in a second place, inside the seventh page script. One bullet was added above, on that line, the three tick boxes behind it and the seventh page script (rules-label.js), and it records v3.1 rather than v3.0 because the words no longer stand in the script: each short phrase now sits on the tick box it describes, as an attribute in the game page's markup, and the script only joins what it finds there. The play-tested bullet gained that line, that script and both versions at its tail; the one-line bullet at the end of the list gained a clause about the same line. No reading of the code, no bullet order and no other word of the list changed, and v3.1 touched no script, no stylesheet and nothing of the game. What v3.3 changed here: the bullet the changelog's v3.3 entry said this page owed was added, on the guard that stops a live run before Clear best scores empties the list (pagekeys.js, its third guard). It records the bug it closes — a mid-run press once left the run moving on unseen behind the cleared screen, with the button under the board reading Start — the capture-phase click on that button, the press of the pause button that goes with it, the two page reads it depends on (that button's id and the overlay's hidden class), the cost of the guard, and the fact that it is a reading rather than a press I have watched. The play-tested bullet gained that guard and v3.3 at its tail, and now counts one more hand copy-over, of pagekeys.js; the one-line bullet at the end of the list gained a clause about the same guard. No reading of the code, no bullet order and no other word of the list changed, and v3.3 touched no file but pagekeys.js. What v3.7 changed here: the debt the changelog's v3.6 entry named is closed — this page said nothing about the tooltips the fifth page script writes on the press-buttons around the board (v3.5), nor about the one it added on Clear best scores (v3.6), because its v2.1 bullet on that script covered the four button words and the two page readings behind them and no more. One bullet was added above, at the head of the list, on those tooltips: which buttons carry one, the words each names, that every word restates a rule already written down on the game page or in the script the rule belongs to, that each title is written from the same state word the file already works out and from no reading of the game's own state, that each element is guarded on its own, and the two costs — a tooltip is shown on hover or on focus and never on a tap, so a touch screen cannot reach the words, and the wording is a second reader of the game page's rules that could drift if a rule changed. The bullet on the button under the board gained a clause in its heading and a sentence at its tail pointing at that new bullet, so the two accounts cannot be read as disagreeing; the play-tested bullet gained the tooltips and both versions at its tail; the one-line bullet at the end of the list gained a clause about the same tooltips and their cost on a touch screen. No reading of the code, no bullet order and no other word of the list changed, and v3.5 and v3.6 touched no file but pause-label.js. What v4.0 changed here: the two debts the changelog's v3.8 and v3.9 entries named are closed. This page's long bullet on the visit's run note still described that line as it was before v3.8 — what it quoted held for a visit with a scored best and for the empty state, but the case where every finished run of the visit has scored nothing was not named — so that bullet's heading gained zero case since v3.8, page synced in the same session and its body gained the sentence recording that the words after best run, and the level clause with them, are written only while the visit's best is above nothing, with no run has scored yet standing in their place otherwise. The bullet on the board's name beside the best figure described the job as keeping the word before the dash and named the em dash as what ends it, while the v3.9 file reads any of the three, so that bullet's heading gained the dash it ends at widened in v3.9 and its body now records the widening, the wrong-looking bar an en dash or a plain hyphen would once have left, and the two cases that did not change (an option text with no dash, or one whose first character is a dash, still stands whole). Two smaller lines of the list were brought into step: the play-tested bullet gained both versions and both files at its tail, with the remark that the game page's own sentence about the run note was written in v3.8's session and needed no change for v3.9; and the closing one-line bullet gained a clause about the run note naming no best run while the visit has none, and a clause about the name now ending at the first of three dashes. The reading of the code behind both bullets is unchanged, the bullet order is unchanged, and no rule of the game, no script and no word of the game page was touched by v4.0, which changed words on this page only.
Where the three archive pages point: the archive for 0.1 to 1.68 was re-synced in v1.97, and the archive for 1.69 to 1.82 and the archive for 1.83 to 1.89 in v1.95, so all three now name this page for the known limits; the changelog page forwards here in one line.