<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitriy Sevryukov</title>
    <description>The latest articles on DEV Community by Dmitriy Sevryukov (@dmitriy_sevryukov_cn).</description>
    <link>https://dev.to/dmitriy_sevryukov_cn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4122193%2F0d54542f-5e4f-4f51-86c0-922d7520012f.png</url>
      <title>DEV Community: Dmitriy Sevryukov</title>
      <link>https://dev.to/dmitriy_sevryukov_cn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmitriy_sevryukov_cn"/>
    <language>en</language>
    <item>
      <title>A legal FEN is not a game: state identity in a browser chess extension</title>
      <dc:creator>Dmitriy Sevryukov</dc:creator>
      <pubDate>Sat, 12 Sep 2026 14:02:50 +0000</pubDate>
      <link>https://dev.to/dmitriy_sevryukov_cn/a-legal-fen-is-not-a-game-state-identity-in-a-browser-chess-extension-140d</link>
      <guid>https://dev.to/dmitriy_sevryukov_cn/a-legal-fen-is-not-a-game-state-identity-in-a-browser-chess-extension-140d</guid>
      <description>&lt;h2&gt;
  
  
  Same FEN, different game
&lt;/h2&gt;

&lt;p&gt;If you key a chess overlay on FEN alone, two different games can look identical. Rematches are where this shows up. The starting position is the same string, the player knows it's a new game, and an old arrow can come back if you never stored anything else.&lt;/p&gt;

&lt;p&gt;Lichess does issue a new game id. In the recorder that's an explicit rematch boundary: a new &lt;code&gt;siteGameId&lt;/code&gt; / session identity doesn't get glued onto the previous unfinished game just because the profile looks the same. The overlay identity is narrower still.&lt;/p&gt;

&lt;p&gt;I work on ChessNavio. It runs Stockfish locally and draws one suggested move next to the board. I spent more time on "is this even a game" and "is this result still about that board" than on the engine itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  querySelector will find you a board that is not a game
&lt;/h2&gt;

&lt;p&gt;Open Chess.com Play Computer as a guest. The pieces can already sit on their starting squares before anyone presses Play. A legal start FEN doesn't tell you much here — that string doesn't change when the game begins. What changes is the chrome beside the board: Resign, hint, undo, the live move list. Those are what we wait for after Play.&lt;/p&gt;

&lt;p&gt;World Chess is the other direction: the page can contain more than one board-like node. We don't pick a winner between two legal FENs. The qualifier wants exactly one primary game board. If that's missing, or there are several, the UI stays quiet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-site checklists
&lt;/h2&gt;

&lt;p&gt;Each site has a short checklist: are we on a route that can host a game, is there exactly one visible board of the expected kind, has the game actually started, do we know the user's side, and is there a local or platform game id when the site exposes one.&lt;/p&gt;

&lt;p&gt;If any of that is missing, nothing renders. I'd rather the panel sit there doing nothing than draw an arrow on a lobby or a broadcast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DOM does not settle in one mutation
&lt;/h2&gt;

&lt;p&gt;After a move, the board node and the chrome around it can update on different frames. The piece may already have moved while the surrounding controls are still catching up, or the board root gets thrown away and rebuilt. We wait until those signals agree. If they don't, we treat it as not ready yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Display-time identity
&lt;/h2&gt;

&lt;p&gt;Once the engine is asynchronous, a result is only allowed to paint if it still matches the board we're looking at. The type we compare at presentation time isn't a FEN and isn't a session snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PresentationIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;boardEpoch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;configRevision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;boardEpoch&lt;/code&gt; ticks when the position we care about changes. &lt;code&gt;jobId&lt;/code&gt; ties the paint to the job that produced it. The user's side and platform game id live on the session snapshot. Side-to-move is already in the FEN.&lt;/p&gt;

&lt;p&gt;UCI &lt;code&gt;stop&lt;/code&gt; asks Stockfish to stop. It still emits &lt;code&gt;bestmove&lt;/code&gt; later, so we can't assume the old search is gone. The result gets displayed only if fen, epoch, job id and config revision all still match.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engine does not live in the service worker
&lt;/h2&gt;

&lt;p&gt;MV3 service workers get stopped when they go idle, which means you can't count on keeping a WASM engine in memory there. Stockfish normally runs in an offscreen document, behind one scheduler. If that transport fails, there's an iframe fallback.&lt;/p&gt;

&lt;p&gt;Priority, from the job reasons in the scheduler: confirmed live position, interactive training, speculative reply, recorded-move verification, post-game enrichment. A confirmed live position preempts speculative and post-game jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays on the machine
&lt;/h2&gt;

&lt;p&gt;Live position, engine lines and the caption next to the move are computed on the device. The server is for the account, and for sync if the user actually turned that on. Finished games don't go to the account until that setting is on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strength
&lt;/h2&gt;

&lt;p&gt;The engine is Stockfish 18 as WebAssembly. Shallower levels mostly search less.&lt;/p&gt;

&lt;p&gt;On the softer levels Maia proposes candidates; Stockfish scores them and we skip ones past the allowed eval loss. Max is plain Stockfish. I'm not mapping that to a rating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Captions
&lt;/h2&gt;

&lt;p&gt;The sentence next to the move is built locally from the position, the recommended move, the continuation and a detected motif. If we don't have a more specific motif, the English line is "improves the position." We don't invent kingside pressure the search never showed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fair play
&lt;/h2&gt;

&lt;p&gt;Platforms prohibit engine help in an ongoing game against a human. The same pipeline against a computer, on a training board, or after the game is analysis. There's no stealth mode and no anti-cheat bypass. Getting a selector to work is not the same as Lichess or Chess.com saying that's allowed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A FEN you can paste without installing anything
&lt;/h2&gt;

&lt;p&gt;You can &lt;a href="https://chessnavio.com/analyze" rel="noopener noreferrer"&gt;paste a PGN or FEN in the browser without an account&lt;/a&gt;. &lt;a href="https://chessnavio.com/how-it-works" rel="noopener noreferrer"&gt;What stays on the device versus the account&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still don't know
&lt;/h2&gt;

&lt;p&gt;I still don't know whether the short note next to the arrow helps people understand the move, or whether they look at the arrow and skip the sentence. Engine strength isn't the open question. The wording is.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I used an assistant to edit this. The session gates and identity types are from the code I ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webassembly</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
