Skip to content
This repository was archived by the owner on Sep 10, 2026. It is now read-only.

Release: version packages - #52

Merged
seenu-k merged 1 commit into
mainfrom
changeset-release/main
Aug 11, 2026
Merged

seenu-k merged 1 commit into
mainfrom
changeset-release/main

Conversation

@eigen-release

@eigen-release eigen-release Bot commented Aug 11, 2026

Copy link
Copy Markdown

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@eigeninteractive/server@0.3.0

Minor Changes

  • #51 d0b28f4 Thanks @seenu-k! - The socket now carries one message, a complete per-seat session snapshot, and every accepted command answers with the same value. roster, sync and frame as separate message kinds are gone.

    This is a breaking wire change, and it fixes a class of bug rather than an instance. The old shape asked a client to assemble one session out of four sources, each carrying a different slice of the truth under a different versioning scheme: an HTTP summary that carried status, unversioned roster messages that stopped at the lobby, a sync on mid-game open that carried only a version, and versioned frame messages that carried neither. Read that list for "how does a client learn the game became active" and the answer is that it cannot. Nothing on the live channel said so. A creator sitting in a full waiting room never saw a Start button, and every seat stayed in the waiting room after the game began, until the screen was disposed and re-entered.

    {
      "type": "session",
      "seq": 7,                    // monotonic per game, incremented by EVERY commit
      "gameId": "…", "shortCode": "ABC123", "access": "private",
      "schemaVersion": 1, "config": { … },
      "turnSeconds": null, "budgetSeconds": null, "incrementSeconds": null,
      "rated": false, "ratingPool": null,
      "minPlayers": 2, "maxPlayers": 2, "createdBy": "…",
      "status": "active",          // what moves
      "players": [ … ],
      "version": 3,                // null in the lobby
      "frame": { … }               // THIS seat's observation; null in the lobby
    }

    It is sent on socket open at every status, and after every committed change, lobby or state. Being complete makes it idempotent: a client that applies the newest one it has seen is correct however many it missed, so there is nothing to reconstruct and no second channel to correlate against. It carries the immutable header as well as the moving parts because a game screen must not need a second source; the D1 summary stays what it always was, the index behind lists and discovery.

    Hidden information is safe by construction rather than by vigilance. The envelope is projected per seat before it is sent, frame is only ever the receiving principal's own seat's view resolved against the roster at send time, and a socket holding no seat gets the envelope with frame: null, which is how a viewer learns the game started at all.

    seq is the new ordering key, because version cannot order a lobby change that has none. Apply a snapshot when seq exceeds the one you hold; that single comparison resolves a command response racing its own socket push, a duplicate delivery, and a reconnect that missed nothing. One clause is added, and it is a property of the state machine rather than an exception: a finished or aborted snapshot applies whatever its seq, because those statuses are absorbing and the abort teardown drops the storage the counter lives in.

    Gap recovery is unchanged and still animates. GET /games/{id}/frames?from=&to= still fills a version jump, and the missing span is played through under the previous envelope, so only the real snapshot may move status, players and seq. A client that missed a finish therefore animates the moves and then shows the outcome, instead of displaying a finished game while mid-game moves play.

    New and changed surface:

    • GET /games/{id}/session returns the snapshot over HTTP, for the paths that have no socket.
    • LobbyAccepted, Joined and Roster are gone. CommandAccepted and SoloStarted are now { session }.
    • RosterSnapshot and SyncMessage are removed from the exported protocol types; SessionSnapshot replaces them. FrameMessage remains, as the payload inside a snapshot and the element type of the range fetch.
    • GameStub gains session(gameId, userId).
    • The Durable Object's meta table gains seq, short_code and outcomes. Pre-1.0 and with no games depending on the engine, these are edited into the init migration in place rather than added as a second one, so local .wrangler state predating this must be deleted. Outcomes are retained rather than drained by the finish compaction, because they are kernel output that no transition row holds, so a cold open of a finished game could not otherwise be answered from the DO alone.

    One incidental fix rides along: frame was typed non-nullable in the generated Dart client, because a nullable $ref lost its null branch on the way into the OpenAPI document. The schema now spells it as a union, so it emits anyOf: [$ref, {type: null}] and generates as Frame?. A lobby session would have crashed the old typing on arrival.

Patch Changes

  • Updated dependencies []:
    • @eigeninteractive/kernel@0.3.0
    • @eigeninteractive/rules@0.3.0

create-eigen-game@0.9.3

Patch Changes

  • #51 e140da5 Thanks @seenu-k! - A scaffolded game now ships the local inspector wired up: pnpm inspect games, pnpm inspect game <id|code>, pnpm inspect do.

    One line makes it work. The inspect script points at eigen-inspect, which the testkit already installs as a devDependency, and which reads the local .wrangler databases through Node's built-in node:sqlite, so there is nothing to install and no native build to approve.

  • #51 8c7d54e Thanks @seenu-k! - test:watch now reruns when you edit only a fixture. It did not before, and the failure was silent: you saved a case, nothing happened, and the suite you were watching kept reporting the last result.

    The cause is that twinFixtureTests reads fixtures with readFileSync at collect time, so they are not in Vite's module graph. Vitest looks a changed path up in that graph, finds nothing, checks whether the file is itself a test file, and reruns nothing. Fixture JSON was never even watched, since Vitest only adds paths outside the graph to its watcher when a trigger glob names them.

    A scaffold therefore now ships server/vitest.config.mts carrying one option:

    forceRerunTriggers: [...configDefaults.forceRerunTriggers, "**/src/module/fixtures/**/*.json"],

    This is what forceRerunTriggers is for. Vitest's own defaults use it for package.json and the config files, which are read outside the graph for the same reason, and it appends setupFiles and snapshotSerializers to the same list.

    Two things worth keeping if you edit the file. Spread the defaults rather than replacing them, because config resolution is a shallow merge and a bare array quietly stops manifest and config edits from triggering a rerun. And leave the trigger off any @cloudflare/vitest-pool-workers project you add later: a match reruns the whole suite, which costs nothing for pure-Node tests and is not free for workerd, where a fixture cannot change the result anyway.

@eigeninteractive/kernel@0.3.0

Patch Changes

  • Updated dependencies []:
    • @eigeninteractive/rules@0.3.0

@eigeninteractive/testkit@0.3.0

Patch Changes

  • #51 e140da5 Thanks @seenu-k! - New eigen-inspect command, for reading a Worker's local .wrangler state from the terminal while you develop a game.

    A game's truth is split across two stores on purpose. D1 is the index: discovery, history, ratings, identity. The game's own Durable Object is the session: the authoritative status, the roster, and the append-only transition log. That split is what makes the engine's read paths cheap, and it is also why a question like "the lobby filled, so why is nothing happening" cannot be answered by looking at one table. The D1 games row is a fire-and-forget display mirror, so it can lag; the DO is the only authority.

    eigen-inspect game <id|code|prefix> joins both, decodes every JSON column, prints the transition log as a timeline, and ends with the sentence you actually wanted:

    Diagnosis     ready and not started: 2/2 seats filled. Nothing happens until the
                  CREATOR calls POST /api/engine/games/{id}/start. A start is explicit
                  and creator-only; filling the lobby does not start a game.
    

    When the two stores disagree it says so as Mirror drift, naming the DO as authoritative, so a lost mirror write reads as a lost mirror write rather than as a mystery. A finished game with surviving outbox rows is called out the same way, since that means the D1 finish apply has not landed and ratings are not published.

    The other commands are games (the index), do (every local Durable Object mapped back to its game id, which the hashed filename cannot tell you), players, tables, and sql for anything unmodelled. --game <ref> points tables and sql at one game's Durable Object instead of D1; --json emits everything for a script or an agent.

    This complements rather than replaces Wrangler's own local browser, which is e in wrangler dev (or /cdn-cgi/explorer) and is the better tool for generic table browsing. eigen-inspect is the one that knows what a game is, and it needs no dev server, browser, or account.

    The reader is also exported as a library at @eigeninteractive/testkit/local-state, so a test or a script can assert against local state directly:

    import { LocalStore } from "@eigeninteractive/testkit/local-state";
    
    const store = LocalStore.open();
    const game = store.game("ABC123");
    expect(game?.meta?.status).toBe("active");
    store.close();

    Every database is opened read-only, so all of this is safe to run against a live wrangler dev. It reads them through Node's built-in node:sqlite, so it adds no dependency, needs no native build, and nothing new reaches your Worker's runtime dependencies. It needs Node 24, which the engine already requires.

  • Updated dependencies []:

    • @eigeninteractive/kernel@0.3.0
    • @eigeninteractive/rules@0.3.0

@eigeninteractive/rules@0.3.0

@seenu-k
seenu-k merged commit d1c5f4e into main Aug 11, 2026
3 checks passed
@seenu-k
seenu-k deleted the changeset-release/main branch August 11, 2026 19:39
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant