@joystack/cli 0.0.0-stage → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +749 -0
  2. package/LICENSE +202 -0
  3. package/README.md +21 -2
  4. package/bin/joystack.mjs +300 -0
  5. package/lib/agent-session.mjs +52 -0
  6. package/lib/agent-surface.mjs +333 -0
  7. package/lib/assets-json.mjs +205 -0
  8. package/lib/assets-page.mjs +184 -0
  9. package/lib/assets-sheet.mjs +365 -0
  10. package/lib/build-id.mjs +130 -0
  11. package/lib/bundle-primitives.mjs +123 -0
  12. package/lib/changelog.mjs +193 -0
  13. package/lib/config.mjs +56 -0
  14. package/lib/create.mjs +243 -0
  15. package/lib/dev.mjs +435 -0
  16. package/lib/engine.mjs +198 -0
  17. package/lib/feedback-sync.mjs +231 -0
  18. package/lib/feedback.mjs +209 -0
  19. package/lib/freeze.mjs +108 -0
  20. package/lib/game-files.mjs +99 -0
  21. package/lib/history.mjs +612 -0
  22. package/lib/inbox-cmd.mjs +57 -0
  23. package/lib/inject.mjs +250 -0
  24. package/lib/joystack-node-doors.mjs +230 -0
  25. package/lib/lane.mjs +141 -0
  26. package/lib/legacy-names.mjs +275 -0
  27. package/lib/lint.mjs +292 -0
  28. package/lib/login.mjs +134 -0
  29. package/lib/model-bake-cli.mjs +17 -0
  30. package/lib/model-bake.mjs +869 -0
  31. package/lib/npm.mjs +38 -0
  32. package/lib/ports.mjs +234 -0
  33. package/lib/primitives.mjs +393 -0
  34. package/lib/printable.mjs +12 -0
  35. package/lib/process-tree.mjs +30 -0
  36. package/lib/project.mjs +84 -0
  37. package/lib/publish.mjs +179 -0
  38. package/lib/replay-cmd.mjs +426 -0
  39. package/lib/replay-export.mjs +1379 -0
  40. package/lib/replay-list.mjs +308 -0
  41. package/lib/replay-profile.mjs +467 -0
  42. package/lib/replay-store.mjs +719 -0
  43. package/lib/room-reload.mjs +168 -0
  44. package/lib/room-server.mjs +539 -0
  45. package/lib/self-update.mjs +162 -0
  46. package/lib/shadow-resolve.mjs +85 -0
  47. package/lib/share.mjs +149 -0
  48. package/lib/smoke-gpu-preload.mjs +91 -0
  49. package/lib/source-path.mjs +33 -0
  50. package/lib/static-server.mjs +1238 -0
  51. package/lib/test-cmd.mjs +697 -0
  52. package/lib/test-policy.mjs +157 -0
  53. package/lib/test-reporter.mjs +177 -0
  54. package/lib/test-selection-cli.mjs +18 -0
  55. package/lib/update.mjs +316 -0
  56. package/package.json +27 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,749 @@
1
+ # Changelog — @joystack/cli
2
+
3
+ The `joystack` command itself. This is the one piece of Joystack you install from npm;
4
+ everything else (the component library, the room primitives, your game's skills)
5
+ lives in the engine and has [its own changelog](https://docs.joystack.ai), which
6
+ you can read with `joystack changelog`.
7
+
8
+ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
9
+ Versions: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
10
+
11
+ > Entries for 0.1.9 and earlier were reconstructed from the release history after
12
+ > the fact, so they are short and may miss detail. Everything from 0.1.10 on is
13
+ > written as the change is made.
14
+
15
+ ## [Unreleased]
16
+
17
+ ## [0.1.44] - 2026-10-09
18
+
19
+ ### Added
20
+
21
+ - When `joystack update` moves a game made before the rename, it ends with a
22
+ short checklist for your agent: the links, tests and publishing tokens in
23
+ your game's own files that the update cannot move itself.
24
+
25
+ ### Removed
26
+
27
+ - `joystack dev` no longer starts a services proxy on port 8788. Nothing in the
28
+ engine sends requests to it, and the port a dev stack needs to find free is
29
+ now only the game's and the multiplayer server's.
30
+
31
+ ### Changed
32
+
33
+ - `joystack create` marks the new game as ES modules (`"type": "module"` in
34
+ package.json), so `joystack publish` no longer prints Node's
35
+ `MODULE_TYPELESS_PACKAGE_JSON` warning.
36
+ - **The CLI is `@joystack/cli`, and its command is `joystack`** (`npx joystack
37
+ dev`, `npx joystack test`, `npx joystack publish`, …). Nothing in your game has
38
+ to change: the previous command name runs the same CLI, so the scripts in your
39
+ package.json keep working, and every earlier name the CLI reads is still
40
+ accepted — the settings block in package.json, the test policy file, the
41
+ game's state folder, environment variables (each `JOYSTACK_*` one falls back
42
+ to its earlier name when unset), the engine doors your code imports and the
43
+ dev server's routes. New games are created with the new names, and moving
44
+ your own code over is optional. A `joystack dev` page sets the signed-in
45
+ identity and the other page globals under their earlier names as well, as a
46
+ published page does, and a variable `joystack test` hands your unit tests or
47
+ smokes is set under both names.
48
+ - **`npx joystack create <name>` starts a new game from any folder.** The
49
+ `joystack` package on npm runs this CLI; your game itself depends on
50
+ `@joystack/cli`, as before.
51
+ - **`joystack update` moves a game made before the rename onto Joystack.** A game whose
52
+ package.json still lists the CLI under its earlier name gets `@joystack/cli`
53
+ in its place, then the newest engine and the Joystack skills, as any update
54
+ does. Your package.json scripts keep working. Imports, lint config and
55
+ handbook paths that name the engine's earlier folder inside `node_modules`
56
+ keep working too: that folder now leads to the installed engine.
57
+ - **A published game lives at its own address, `https://<slug>.joystack.games/`.**
58
+ `joystack publish` prints that address, and the CLI talks to
59
+ `https://api.joystack.ai` unless you point it elsewhere.
60
+
61
+ ### Added
62
+
63
+ - **`joystack logout` signs you out on the platform too.** The token is revoked,
64
+ so a copy of it left anywhere stops working, and `joystack logout --all`
65
+ signs you out on every machine at once.
66
+
67
+ ### Fixed
68
+
69
+ - `joystack changelog` works on a signed-in machine. It reads the engine's
70
+ release list with your sign-in, and on a machine that is not signed in it
71
+ says to run `joystack login` instead of failing with an HTTP error.
72
+
73
+ ## [0.1.43] - 2026-10-03
74
+
75
+ ### Added
76
+
77
+ - **`looop dev` serves your game as it was at any commit, for the Assets
78
+ page's model history.** `/__looop/history?path=…` lists the commits that
79
+ touched the files named, and `/__looop/history/<commit>/<file>` serves a
80
+ file of your game's folder at that commit, from a temporary read-only copy
81
+ made with `git archive`: nothing is checked out, and your working copy,
82
+ branch and index are never touched. A commit is named by its hex id only,
83
+ and only a commit in the history of what you have checked out is served
84
+ (not one on another branch, nor one fetched and never merged); only your
85
+ game's own folder is served (hidden files and pages never are);
86
+ and only a browser on the same machine may read it. At most four commits
87
+ are kept extracted at once, each for up to half an hour, and all of them
88
+ are removed when `looop dev` stops.
89
+ - **`looop assets --json [<id>…]` prints what your assets are, for an agent.**
90
+ For each declared asset (or just the ones named): id, label, stage, file
91
+ type, its parameters with their `describe`, the stats measured from what it
92
+ built (triangles, bones, size, palette…), a sound's length, flags (rigged,
93
+ clips, emissive) and its reference image paths. The numbers are the Assets
94
+ page's own, measured headless on the `looop dev` already running or on one
95
+ started for the command. An unknown id is an error listing the declared
96
+ ones; an asset that fails to build is listed with its error and the command
97
+ exits 1.
98
+
99
+ - **`looop assets <id> --sheet` draws a view sheet without opening anything.**
100
+ It writes `.looop/sheets/<id>-sheet.png` (every side at one scale, a ruler,
101
+ the in-game view and the reference images) and a `.json` report of what was
102
+ measured beside it. `--set k=v,…` draws it at other values and refuses one
103
+ the asset does not allow, `--tag` names the files, and `--against` an earlier
104
+ sheet's `.json` also writes the two side by side with what moved. It uses
105
+ the `looop dev` already running, or starts one for as long as it takes, and
106
+ exits with an error when a reference image is missing.
107
+
108
+ ### Fixed
109
+
110
+ - **`looop test` no longer fails a new game's boot smoke on its first run.**
111
+ `looop dev` (and the stack `looop test` starts) now reports ready only once
112
+ the local multiplayer server answers. Before that, a smoke could open the
113
+ page while the server was still starting, see a refused WebSocket
114
+ connection in the console and fail, even though the game connected a moment
115
+ later. If the multiplayer server stops while starting, `looop dev` now stops
116
+ with an error saying so.
117
+ - **`looop assets --json` and `--sheet` on an older engine say what to do.**
118
+ On a game whose engine is too old for them, both commands now stop with a
119
+ message naming the engine version the game has and telling you to run
120
+ `looop update`, instead of failing with "is not a function".
121
+ - **Error messages from `looop` never carry terminal escape sequences.** Text
122
+ a command quotes from your game (an asset's label, an id, the stats on a
123
+ view sheet) is printed with control characters replaced, so a game cannot
124
+ make your terminal change colours, titles or the clipboard. A stat on a
125
+ view sheet that is not a number prints as `?`.
126
+ - **`looop assets --sheet` writes only where it says it does.** A
127
+ `.looop` or `.looop/sheets` that is a symbolic link is refused (choose a
128
+ folder with `--out` instead), and each file is written under a fresh random
129
+ temporary name that nothing already at that name can redirect.
130
+ - **The model history served by `looop dev` follows what you check out.** A
131
+ commit is served only while it is in the history of what is checked out
132
+ now, so after you switch branches a commit the new branch does not contain
133
+ is no longer served.
134
+ - **`looop assets` no longer stops the `looop dev` you are using.** With a
135
+ `looop dev` already running for the same game, `looop assets` opens the
136
+ Assets page on it and exits, instead of shutting it down to start its own.
137
+ It starts a dev stack only when none is running for the game, and replaces a
138
+ running one only if that one does not answer for the Assets page. A reused
139
+ stack keeps its own agent: notes sent from the page reach the agent that
140
+ started `looop dev`, and `looop assets` says so when that stack has none.
141
+
142
+ ### Changed
143
+
144
+ - **`looop dev` and `looop assets` reload when a model or sound changes.**
145
+ Saving a `.glb`, `.gltf`, `.bin`, `.wav`, `.mp3`, `.ogg`, `.m4a` or `.flac`
146
+ in your game reloads the open page, the same as editing a source file, so a
147
+ re-exported model shows up on the Assets page without a manual refresh.
148
+ `.m4a` and `.flac` files are also served with their audio content types.
149
+
150
+ ## [0.1.42] - 2026-09-24
151
+
152
+ ### Added
153
+
154
+ - **`looop lint` runs the engine's handbook reference check** when the
155
+ installed engine carries it: a file your `handbook/` names that no longer
156
+ exists fails lint, with its chapter and line. Older engines skip the check.
157
+
158
+ ## [0.1.41] - 2026-09-24
159
+
160
+ ### Added
161
+
162
+ - **`looop publish --dev-tools` publishes a review build.** Use it with
163
+ `--slug` to publish a copy of your game whose link carries the dev toolbox,
164
+ so whoever opens it can use your Tweaks (jump to a level, change a number)
165
+ without running `looop dev`. It is refused for your game's own slug, so the
166
+ live game never carries dev tools, and republishing a slug without the flag
167
+ makes it a normal publish again. Tweaks only appear on a review build once
168
+ your game is on an engine release that supports them (see `looop changelog`).
169
+
170
+ ## [0.1.40] - 2026-09-23
171
+
172
+ ### Added
173
+
174
+ - **Dev tools can now reach your agent directly.** When your coding agent
175
+ starts `looop dev` itself, a request you type into a dev tool in your
176
+ browser also arrives in that agent's conversation, waking it if it was idle,
177
+ instead of only landing as a file under `.looop/agent-inbox/`. Reports a
178
+ tool files without a request are still only filed. It reaches exactly the
179
+ conversation that started the dev server; no flag or setup is needed. Works
180
+ with Claude Code in any of its apps; other agents still get the file.
181
+ Reports from a phone on your wifi or a `looop dev --share` tester are filed
182
+ but never wake your agent.
183
+ - `looop inbox reply <id> "<text>"` — how the agent answers a report; the text
184
+ shows up in the tool that sent it, and is saved into the report file.
185
+ - Agent-inbox reports can carry a `message` (what you typed for the agent) and
186
+ a PNG `image`, saved beside the report. Each report file now has an `id`.
187
+ Reports from your own machine may be up to 8 MB, to fit a screenshot;
188
+ reports from other devices keep the 2 MB limit and cannot carry an image.
189
+ Your agent is woken at most once a second; a request sent sooner waits its
190
+ turn, and past three waiting, the rest are only filed.
191
+ - `looop test` never wakes your agent, even when the agent is the one running
192
+ it.
193
+
194
+ ### Changed
195
+
196
+ - **`looop replay list` counts a frame late when it missed the screen's refresh, not the device's typical frame.** The budget was each browser's median frame interval, so a phone that stayed slow for a whole session was judged against its own slow frame: one session read `4,457 of 12,982 frames late (budget 24.9 ms)` on a 120 Hz screen. The budget is now the screen's refresh, read off the frames, and the same session reads `12,453 of 12,982 frames late (budget 8.3 ms)`. Expect higher late counts on slow devices; a device that keeps up reads as before.
197
+
198
+ ## [0.1.39] - 2026-09-19
199
+
200
+ ### Added
201
+
202
+ - **`looop dev --no-reload` runs your game exactly as it was when you started it.** Nothing you save reaches anyone until you restart `looop dev`: no page reloads, a page opened later gets the same version, and a multiplayer room is never rebuilt or restarted. It is how you keep editing while testers play (`looop dev --share --no-reload`); sessions recorded meanwhile are stamped with the build you started, so replays match what was played. It serves a copy of the game taken at startup, kept in `.looop/frozen/` and deleted when `looop dev` stops; on a Mac the copy is instant and takes no extra space.
203
+ - **`looop dev --share` prints a public link your testers can play.** Send it to a friend; whatever they play on their phone is recorded into your `.looop/replays/` with their device's performance, the same as your own sessions, so you can find their stutter with `looop replay list` instead of asking for a report. Multiplayer works through the same link, and each person who opens it is their own player. It needs `cloudflared` (`brew install cloudflared`, or `winget install --id Cloudflare.cloudflared`), and it prints a warning as it gives you the link: anyone holding it can reach your dev server as if they were on your wifi, so send it only to people you trust. The link lasts while `looop dev` runs. Saving a file reloads their page as it reloads yours. LLM and leaderboard calls do not work for testers yet. It needs the engine release this ships beside, and says so on an older one. See `shared/docs/replay.md`, "Recording other people's sessions".
204
+ - **`looop lint` now tells you when your game does not build.** On a framework-v2 game it runs the build gate and prints every refusal at its file and line, alongside the rule checks, and goes red. Before this, a game whose trait broke a build rule looked fine to lint and surfaced only as a test that could not open its world — which reads as a failing test. Lint is where "your code breaks a rule" is answered, so it is answered there. An engine with no gate in it skips the step rather than failing, the same way the other checks do.
205
+ - **`looop test` leaves its result on disk.** Every run writes a verdict (`.looop/test/last.json`: green or red, every failure with its message, every skip with its reason, how long each file took) and the complete output (`.looop/test/last.log`). Read those instead of piping the command through `tail` or `grep` — a pipe hides the exit code and cuts off the part that says what broke. The last 20 runs are kept beside them.
206
+ - **`looop test --help`** — the options, what counts as a test file, the environment variables, and where the result lands.
207
+ - **Declared platform skips.** A test file whose head carries `// skip-on: win32 — <why>` is not run on that platform, is listed as skipped with its reason, and leaves the run green. A skip with no reason fails the run. This is the only sanctioned way for a test not to be green: a test that is red everywhere gets fixed or deleted.
208
+ - **A smoke that never exits is stopped.** After 10 minutes (`LOOOP_TEST_SMOKE_TIMEOUT=<seconds>` to change it) the smoke and everything it started are terminated, it is reported as hung, and the remaining smokes still run.
209
+ - **The node tests are stopped too** if they are still running after 30 minutes (`LOOOP_TEST_UNIT_TIMEOUT=<seconds>`): a test whose promise never settles ends the run as hung instead of holding the terminal forever. Both timeouts say so when their value is not a number of seconds, instead of ignoring it.
210
+ - **Three silent greens are now red.** A test file that ends the process before reporting a test (`process.exit()` at module level) fails as a whole — node counts it as a pass. A file that cannot be loaded shows its syntax or import error instead of the two words "test failed". A `skip-on` line naming something that is not a platform (`windows`, `win32-only`) fails the run instead of being ignored.
211
+ - **A run that cannot finish still leaves a verdict.** If the test server will not start, or `looop.tests.json` cannot be read, `last.json` says `ok: false` with the cause in `error` — it never keeps describing the previous, possibly green, run.
212
+ - **`todo` tests are listed and counted**, so the totals line always adds up.
213
+ - **Node tests are timed per file**, and the slowest are named at the end of the run.
214
+ - **A run records how it was run.** The verdict carries the command, the node version, and how busy the machine was at launch (`machine.cpuBusy`); when it was more than half busy the run says so in its first lines. A suite that takes four times as long next to another test run or a build has not become slower, and now the two can be told apart afterwards.
215
+ - **Smokes are counted by kind.** The smoke line of every run ends with how many smokes declare each `browser-need` and how long they took (`layout 3 4.1s, pixels 2 38.0s, undeclared 1 2.2s`), so a growing number of browser smokes — and which kind the time goes to — is visible on the run that added one.
216
+ - **`looop test` survives a lint step that cannot run.** A lint failure to start (a broken engine install, for one) is reported as a failed step; the tests still run and the verdict is still written. `looop lint` itself now fails, with the repair, when the installed engine's build gate cannot be loaded, instead of stopping the command.
217
+ - **The suite's total, against a budget.** A run of the whole suite ends with its total time against a budget — 300 seconds, or `"budgetSeconds"` in `looop.tests.json`. Going over never fails the run; it is there so the cost is visible before another test file is added.
218
+ - **Running a test file with bare `node` now says what to run instead.** `node crown.test.mjs` used to fail with `Cannot find package 'looop'` — true (the `looop` doors are resolved by something the CLI installs before it runs a test) and no help at all. It now names the door that was imported and the command: `npx looop test <name>`. Nothing about your game changes; the CLI leaves a small `node_modules/looop` package behind to carry the message.
219
+
220
+ ### Changed
221
+
222
+ - **`looop test` prints failures in full and everything else as counts.** A passing test is no longer listed by name on the console (the full listing is in the log), and the run ends with one block: totals, every failed file, and the paths of the log and verdict.
223
+ - **Windows: smokes render on the GPU.** Headless Chromium is now started with the D3D11 backend named outright; with the default backend it did not reach the GPU and rendered a 3D scene slower than software (measured on one scene: 5.8 → 57 frames per second). `LOOOP_TEST_ANGLE=<backend>` picks another backend, `LOOOP_TEST_ANGLE=default` restores Chromium's own choice.
224
+ - **Deleting or renaming a test no longer widens `looop test --since` to the whole suite.** A removed test file is recognised by its name; only a change to a non-test file that no rule covers still selects everything.
225
+ - **`looop lint` no longer ends on a green line under a failed build gate.** With no rule errors but a failed gate or entity check, the closing line is red and names the step.
226
+ - **Stopping `looop test` (Ctrl-C) stops what it started**, and a smoke that leaves a helper process running no longer keeps the run waiting for it.
227
+ - A run's own files under `.looop/` never count as a change for `--since`, so a game that does not ignore that folder is not widened to the whole suite on every run.
228
+ - The extended tests a run includes or defers are stated in one line; `--list` still gives the reason for each. A scoped run (`looop test <pattern>`) does not mention them at all.
229
+
230
+ ## [0.1.38] - 2026-09-18
231
+
232
+ - **`looop replay export` answers "why did it stutter" with SQL.** Beside the world at every tick and the inputs, a session recorded while the Performance tool was measuring gets `<session>.perf.parquet`: one row per frame, per finished second, per entity kind per second, and per long frame Chrome recorded (`row` says which), with `at_ms` (page time), `tick_from` and `tick_to` to join on, the phase split, the counters that moved in that frame (shaders compiled, uploads, spawns and despawns by kind, the heap delta, channel deliveries, draw calls and triangles), and a long frame's `blocking_ms`, `render_start`, `layout_start` and the `scripts` that ran in it. A `kind` row is one kind's share of a second as columns you can group by — `ms`, split into `tick_ms` and `present_ms`, `trait_calls`, `entities_spent`, and `alive` and `alive_max`, how many of that kind the replayed world held over the second on average and at most — so cost per entity is a division, not a JSON unpacking.
233
+ - **Every export also writes `<session>.fields.parquet`: what each state column and each channel is.** One row per column and per channel of every kind, naming the trait behind it, the name the kind gave that trait, the field's own name, type and default, and the file and line it is declared on; a channel row adds `senders`, every query declared to send on it (kind, trait and line) and whether players' browsers can. A column `hull_cracks` and a channel `rock-hits_crack` share no prefix; this table says both belong to the trait the kind calls `hull`, and in which file. The trait, instance, source and senders columns need the engine release this ships beside; on an older engine they are empty, and so are the profile's `trait` and `instance`.
234
+ - **`looop replay list`** lists every recorded session, newest first: when it was played and for how long, how many players, the device and browser that measured it — and whether a test or script drove that browser — how many of its frames were late and where its worst stutter in play was, and the engine, build and commit that recorded it, with whether that build is the one in your folder now. Nothing is replayed, so it answers in a moment. Sessions under a second are left out; `--json` prints every one as data.
235
+ - **A build id names exactly which version of your game ran.** `looop build-id` prints one short hash of the game's files — the files `looop publish` uploads — with the git commit they came from and whether they have uncommitted edits. Any edit changes it, a constant inside a tick included. `looop dev` stamps each recorded session with its build, `looop replay list` and the export's info table show it, the export warns when the game's files have changed since the session was recorded, and `looop publish` prints the build id of what it uploads. `shared/docs/build-id.md` has the details.
236
+ - **Every export also writes `<session>.info.parquet`**, what is true of the session and of stretches of it that no other row can say: its span, tick rate and start time; each player's stay in the room; each browser's device (user agent, viewport, screen ratio, cores, touch, whether it keeps a long-frame record at all, and whether automation drove it); and, as tick ranges you join against, the stretches a browser measured nothing, the tick a track stopped being kept (`looop dev` keeps up to 512 MB of one session's track, several hours of play, and the page stops sending what would be refused), and the ticks where the replayed world is not the one played — after a divergence, an event a replay cannot reproduce, or a missing segment. Its `session` row carries the recorded and replayed build (`recorded_build`, `recorded_commit`, `recorded_dirty`, `replayed_build`, `build_changed`). A player has one `conn` in every table, so a device's frames join to that player's inputs. `shared/docs/debugging.md`, "Reading a recorded session", has the queries that find a stutter and name its cause, and the cost, info and profile tables column by column; `shared/docs/replay-export.md` has the state, inputs and fields tables.
237
+ - **`looop replay profile [<session>] --ticks 1195-1228`** re-runs those ticks of a recorded session under a CPU profiler and writes `<session>.profile.1195-1228.parquet`: which traits, functions and lines the simulation spent its time in, with each one's share of the samples. A `trait` row is one tick or channel handler — its kind, trait and instance. A `function` row names the handler it ran under, its source relative to your game or to the engine's package, and the innermost function of your game's own code it ran under (`game_fn`, `game_source`), so the engine code your tick called adds up under your tick. A `line` row is a line samples landed on — the loop inside the function, not just the function. It warns below 300 samples; `--repeat <n>` runs the same ticks n times and sums them. The shares are your machine's, never the device's milliseconds.
238
+ - `looop replay export --out <folder>` and `looop replay profile --out <folder>` write the tables anywhere outside your game folder. A plain folder inside the game is refused, because the tables would be published with the game and change its build id; `.looop/` and any folder your `.gitignore` excludes are fine.
239
+ - When an export's replay drifts from the recording, the warning counts each drift once, names its ticks and how many checksums failed inside it, and says why as far as it can tell: the game changed, the game's files changed, or — when the build is the one that recorded the session — the game did not replay the same way twice, which is a fault in the game rather than an edit.
240
+ - `looop dev` keeps up to 3 GB of recordings per game, up from 200 MB, so a session of an hour or more is no longer swept at the next start along with everything older. The newest 20 are still kept either way.
241
+
242
+ ### Fixed
243
+
244
+ - `looop replay export` prints what a recording names — an event's kind, a player's id — with control characters removed, and so does `looop replay profile`. A recording is a file anyone can hand you, and an escape sequence in one could repaint or retitle your terminal.
245
+ - `looop replay export`: object and list columns — `payload`, and a declared list or object field — are stored as JSON rather than as a JSON string of the JSON. `json_extract(payload, '$.dx')` and `payload->>'$.dx'` now answer directly; before, they returned null unless the string was parsed a second time.
246
+ - `looop dev` no longer files another game's sessions under yours. A tab stays open after you stop `looop dev` in one game folder and start it in another on the same port, and it kept sending its recording to the new server, which stored it in the wrong game's `.looop/replays/` — a session that game cannot replay, counted against its space for recordings. The dev server now refuses a page of a game it isn't serving. A tab from this engine release on stops sending and says so once in its console; a tab from an older engine is refused on every segment it sends.
247
+
248
+ ## [0.1.37] - 2026-09-06
249
+
250
+ - Windows: stopping a development or smoke stack terminates its worker descendants, releasing their ports and temporary folders.
251
+
252
+ - Windows: selective test runs recognize short and long names for the same project folder, and replay export accepts destinations inside the game folder.
253
+
254
+ - Test submissions with `looop test --since <base>`: ordinary checks plus explicitly triggered extended tests in `looop.tests.json`; `--list` explains selection and `--all` runs an explicit audit. Smoke stacks disable reload watchers, and module serving avoids repeated filesystem lookups within each dependency walk.
255
+
256
+ ## [0.1.36] - 2026-09-04
257
+
258
+ ### Added
259
+
260
+ - **`looop dev` keeps the sessions you play.** As you play, the room hands the dev server each slice of the recording and it lands under your game's `.looop/replays/`. Watch one back by adding `?replay=<id>` to the game's URL — the id is printed to the console when the session starts. Nothing leaves your machine, and nothing is kept when the room is not a dev room.
261
+ - **`?replay=last`** — the newest recording, resolved by the dev server, so watching the session you just played does not start with copying an id.
262
+ - **Old recordings are swept, so a game folder stops growing forever.** When `looop dev` starts it keeps the newest **20 recordings** and up to **200 MB** per game, whichever runs out first — roughly an hour of play for a busy game — and removes the oldest past that, saying on the way up how many went. To protect one, hit **keep** on its row in the toolbox's Sessions panel; a kept recording is never swept. (It still counts toward the caps: keeping means "don't delete this", not "don't charge me for it".)
263
+ - **`looop replay export` — a session you played, as a table you can query.** A recording holds what you PRESSED, not what happened: no position, no speed, no score is in it anywhere. So this replays the session through your own game, reads the world at every tick, and writes it down as Parquet — one row per tick per entity, one column per declared field, plus a second table of every input you sent. Running the game costs a few seconds once; every question you ask afterwards is free, in SQL, with DuckDB or pandas or anything else that reads Parquet. Measured on a real 27-second session: 279,367 rows x 443 columns in 10 seconds, 3.9 MB on disk, and a query answered in 0.02 s. Numbers come back bit-for-bit as the simulation held them. `npx looop replay export --help` for the schema, or `shared/docs/replay-export.md`.
264
+ - **`looop replay export` tells you when the table will not be the session you played — before it spends the time building it.** A recording holds what you pressed, not what happened, so the numbers come from running your current code on old inputs. The export now compares the recording against what you are running and says up front if the engine version or the shape of your game has moved since. While it replays it also reports how many keyframes put playback back onto the recorded state, and the divergence warning no longer claims that everything after a divergence is worthless — a drift is bounded to the stretch before the next keyframe, and saying otherwise had people binning tables that were mostly fine.
265
+ - The dev server answers the toolbox's Sessions panel — listing this game's recordings, deleting one, and pinning one. Only ever on the dev server, and deleting or pinning a recording works only from a page this dev server itself served. `looop dev` listens on every network interface so your phone can play, which means everything else on that wifi can reach it too — on a cafe or coworking network that is not a set of machines you control, and nothing there can now remove one of your sessions.
266
+
267
+ ## [0.1.35] - 2026-09-02
268
+
269
+ ### Added
270
+
271
+ - Reports landing in `.looop/agent-inbox/` are stamped with `engine`, the engine version `looop dev` was serving when the report was made — so a report read later, after an update, still says which engine produced the numbers.
272
+
273
+ ## [0.1.34] - 2026-09-02
274
+
275
+ ### Fixed
276
+
277
+ - `looop test` on Windows no longer discovers and runs the engine's own scaffold smokes from `node_modules` (or anything under `overrides/` and dot-folders). The gate could never go green there because those smokes always failed; it now runs only your game's tests, as on macOS and Linux. A test filter typed as `sub/depth` also matches on Windows now.
278
+ - `looop dev`, `looop test` and `looop publish` on Windows no longer fail with `Could not resolve "./entitiessaucer.js"` when a game has entity kinds: the generated room module now writes import paths with forward slashes whichever engine version produced them.
279
+
280
+ ## [0.1.33] - 2026-08-27
281
+
282
+ ### Added
283
+ - **The dev server now carries an agent inbox** — `POST /__looop/agent-inbox`.
284
+ Dev tools in the running game (starting with the Performance tool's "Send to
285
+ agent" button) can ship a JSON report out of the browser; it lands as one
286
+ file under your game's `.looop/agent-inbox/` and prints one line in the
287
+ `looop dev` terminal. That folder is where your agent looks when you send it
288
+ something from inside the game — it ignores itself in git, so reports never
289
+ clutter `git status`. Only pages served by your own dev server can write to
290
+ it (cross-origin posts from other websites are refused), and single reports
291
+ are capped at 2 MB. There's no cleanup/rotation yet — reports are small and
292
+ yours to delete.
293
+
294
+ ## [0.1.32] - 2026-08-23
295
+
296
+ ### Fixed
297
+ - `looop dev` no longer hands your game a **browser-blocked port**. It derives the
298
+ multiplayer and services ports from the game port, and a few combinations land
299
+ on ports browsers refuse to connect to (`ERR_UNSAFE_PORT` — e.g. game port 8050
300
+ derives multiplayer port 2049/NFS). The game booted but its room socket died
301
+ instantly, so it rendered blank with no owned player — and because `curl` and
302
+ headless tests ignore the blocklist, every check looked healthy. Dev now skips
303
+ any port block a browser can't reach, and warns if you pin an unreachable one
304
+ with `--port`.
305
+
306
+ ## [0.1.31] - 2026-08-21
307
+
308
+ ### Changed
309
+ - `looop dev` no longer starts a multiplayer server for a **single-player** game.
310
+ A game with no `config({ multiplayer: true })` runs its whole simulation in the
311
+ browser, so the local PartyKit server was dead weight — dev now skips it and
312
+ says so. Structural hot-reload (adding an entity/trait) still works. A
313
+ multiplayer game is unaffected.
314
+
315
+ ## [0.1.30] - 2026-08-21
316
+
317
+ ### Fixed
318
+ - `looop dev` now resolves `three` for a 3D game with **no import map of its
319
+ own** — it maps the bare `three` specifier to the engine's bundled copy, the
320
+ same as the published serve path. Before this, deleting your CDN three import
321
+ map (as engine 0.2.3 tells you to) white-screened the game under `looop dev`,
322
+ because the dev server didn't yet supply `three`. Requires engine 0.2.3+.
323
+
324
+ ## [0.1.29] - 2026-08-19
325
+
326
+ ### Added
327
+ - **Framework v2 (the trait model) support across the whole CLI.** `looop
328
+ create` now scaffolds a v2 game: entities and traits as plain functions
329
+ importing only from the `looop` doors — no manifest files, no `/shared/ui`
330
+ imports. `looop dev`, `looop test`, and `looop publish` run v2 games: the
331
+ dev stack injects the `looop` import doors (`looop`, `looop/traits`,
332
+ `looop/client`, …) into the browser import map, serves the game off the
333
+ same lowered graph the room embeds, and `publish` refuses up front when a
334
+ game imports a door its pinned engine doesn't carry (previously it would
335
+ publish and then fail silently at serve). v2 games pair with engine 0.2.0
336
+ (`npx looop update`); v1 games are untouched. The engine side of v2 is in
337
+ the engine changelog.
338
+ - `looop dev` now actually runs **your game's server-side code**. Until now
339
+ dev fell through to the engine's stock room server, so a game's own
340
+ primitives were silently skipped on your machine and only ran in
341
+ production — the worst way round. When a game has server code, dev builds
342
+ a per-game room server for it and runs that instead.
343
+ - `looop test` now preloads the engine's module resolver into your unit tests
344
+ and smokes, so a test file can **`import { openWorld } from 'looop/test'` at
345
+ the top** like any other import — no per-file bootstrap. (Games on an engine
346
+ too old to ship the resolver run exactly as before.)
347
+
348
+ ## [0.1.28] - 2026-08-18
349
+
350
+ ### Added
351
+ - `looop update --rc <version>` takes a specific **release candidate** — a
352
+ pre-release engine (e.g. `0.2.0-rc.1`) you've been handed to test before it
353
+ becomes an official release. It installs that exact version and re-pins your
354
+ game to it. Candidates are unlisted: a plain `looop update` never picks one
355
+ up, and they don't appear in `looop changelog`. A candidate can be re-cut
356
+ under the same name while it's iterated on, so `--rc` always re-downloads it
357
+ rather than trusting a cached copy.
358
+
359
+ ## [0.1.27] - 2026-08-09
360
+
361
+ ### Added
362
+ - `looop dev` now injects the **dev toolbox** into every game it serves — the
363
+ looop chip / `Ctrl/Cmd+Shift+L` overlay with the Inspector, your tweaks, and
364
+ a link to the Assets page (see the engine changelog for what is inside). The
365
+ tag is added only when your pinned engine actually ships the toolbox, so an
366
+ older engine just keeps working until you `looop update`. `?_toolbox=off`
367
+ hides it for a session. Published games are untouched — this exists only on
368
+ your dev server.
369
+
370
+ ### Changed
371
+ - **BREAKING** `looop inspect` is now `looop assets`.
372
+ Migration: same command, same arguments — `looop assets [<id>]` opens the
373
+ dev stack on the Assets page (the renamed asset inspector). The old verb is
374
+ gone; "Inspector" now names the toolbox's live-world tool instead.
375
+
376
+ ## [0.1.26] - 2026-08-07
377
+
378
+ ### Added
379
+
380
+ - `looop inspect [<id>]` — runs the dev stack and opens the asset inspector on
381
+ it, so you can look at and drive one model or sound at a time. Name an asset
382
+ to open straight onto it; `--no-open` prints the URL instead of launching a
383
+ browser.
384
+
385
+ - `looop dev` now prints the inspector's link when your game declares assets (it
386
+ has an `assets.js`). Not on every boot: a line that always appears is one
387
+ nobody reads by the third time.
388
+
389
+ - `LOOOP_ENGINE_DIR=<path>` runs against an engine **checkout** where it lies,
390
+ installing nothing — `dev`, `test`, `lint` and the automatic model re-bake all
391
+ resolve the same one, so what runs in the browser and what the gate checks
392
+ cannot be two different engines. Only useful if you are working on the engine
393
+ itself: `LOOOP_ENGINE_TARBALL` installs into your project, so trying a
394
+ work-in-progress engine used to mean deleting your game's working engine and
395
+ putting it back afterwards. This leaves your project exactly as it was. Point
396
+ it at an engine bundle (`packages/engine/dist`); a path that is not one fails
397
+ loudly rather than quietly falling back to the installed copy.
398
+
399
+ ### Fixed
400
+
401
+ - **`looop publish` no longer ships files your `.gitignore` excludes.** Build
402
+ output, scratch files and the screenshots a test run leaves behind were being
403
+ uploaded and served from your public game URL. If your `.gitignore` says a path
404
+ is not part of the game, publish now agrees with it. (A game folder that isn't
405
+ a git repo publishes exactly as before.)
406
+
407
+ - `looop inspect --port <n>` opened the inspector on an asset named after the
408
+ port number instead of on your game's first asset.
409
+
410
+ - The dev server no longer injects the game platform layer (the boot gate, the
411
+ loading curtain, the identity menu) into HTML pages that are not your game.
412
+ Only `/games/<slug>/` gets it, which is what happens in production — before
413
+ this, any other page the dev server served came up behind a curtain waiting
414
+ for an identity it had never asked for.
415
+
416
+ ### Changed
417
+
418
+ - The automatic model re-bake that `dev`, `test` and `publish` run now finds
419
+ your models in `assets.js` rather than by scanning `entities/**/entity.json`
420
+ for `.glb` strings — entities name a model by asset id, so there is no path
421
+ left in one to scan. It is also the more complete list: a model your game
422
+ loads from its own code is re-baked now too, where before only a model some
423
+ entity referenced was.
424
+
425
+ ## [0.1.25] - 2026-08-01
426
+
427
+ ### Fixed
428
+
429
+ - `looop dev` now rebuilds and restarts the multiplayer room when you edit a
430
+ file the room depends on — your primitives and their helpers, entity data
431
+ (`entities/`), components, and `overrides/`. Before, only the page reloaded:
432
+ the room kept adjudicating with your pre-edit code and pre-edit spawned
433
+ entities until you killed and relaunched the stack, so a change could look
434
+ like it did nothing. Room state resets on such an edit (the world reseeds
435
+ when the page rejoins); if the rebuild fails, the previous room keeps
436
+ running and the error is printed. This also means a game that gains its
437
+ first primitive while `dev` is running now gets its own room server
438
+ immediately, instead of silently staying on the stock one.
439
+ - `looop dev` could keep running your pre-edit code after you changed a file,
440
+ with no error anywhere — the game just looked like your change did nothing.
441
+ Import URLs are now stamped so that editing any file renames the URL of every
442
+ module that (directly or indirectly) imports it, which forces the browser to
443
+ refetch the changed part of the game on the next reload instead of silently
444
+ reusing what it had cached.
445
+
446
+ ## [0.1.24] - 2026-08-01
447
+
448
+ ### Changed
449
+
450
+ - `looop update` now always installs Looop's version of the skill files and
451
+ the `AGENTS.md` managed block, and removes retired ones — even where they
452
+ were changed locally. Before, an edited file was kept and silently stopped
453
+ receiving updates forever; one unnoticed agent edit could freeze a skill
454
+ out of every future release. Nothing is lost when this happens: the update
455
+ marks each such file `OVERWROTE`, saves the previous copy in
456
+ `.looop/backup-<version>/` (git has it too), and the `/update-looop` skill
457
+ walks you through keeping anything that mattered. Guidance you want kept
458
+ belongs in `AGENTS.md` below the managed block, where updates never touch
459
+ it. Repos already stuck with a frozen skill heal themselves on their next
460
+ update — no action needed.
461
+
462
+ ## [0.1.23] - 2026-08-01
463
+
464
+ ### Changed
465
+
466
+ - When the platform refuses an engine download because the Engine License has
467
+ been updated, `looop dev` and `looop update` now explain why and open the
468
+ sign-in flow for you — review and accept in the browser, and the download
469
+ continues by itself. No more bare HTTP status codes, no manual
470
+ `looop login`.
471
+
472
+ ## [0.1.22] - 2026-08-01
473
+
474
+ ### Added
475
+ - `looop feedback` now brings the conversation back: after sending your unsent
476
+ reports, it pulls each report's outcome from the Looop team and stamps it into
477
+ the report file itself — `status:` / `resolved-in:` in the frontmatter, team
478
+ messages appended under `## Updates`. The file under `notes/feedback/` is the
479
+ whole thread, readable offline.
480
+ - You can reply on a report: add a `## Reply (unsent)` section to a sent report
481
+ file and run `looop feedback` — the reply joins the same thread, and a closed
482
+ report you reply to is automatically reopened on the Looop side. Reply when
483
+ something still needs doing; a new problem is a new report.
484
+ - `looop update` now tells you when a report you filed was resolved in one of
485
+ the versions you just crossed ("Feedback you filed landed in this update"),
486
+ next to the changelog it already prints.
487
+
488
+ ## [0.1.21] - 2026-07-30
489
+
490
+ ### Added
491
+
492
+ - **`looop model bake` resolves a pivot for the model.** `<model>.bake.json`
493
+ may declare `"pivot": "origin"` (default) | `"center"` | `"feet"` |
494
+ `[x, y, z]` — the model-space point that lands on your entity's
495
+ `body.(x,y,z)`. The bake resolves it against the bind-pose bounding box,
496
+ writes it into the skeleton artifact, and measures the broadphase radius
497
+ about it (a sphere centred on the pivot but measured from the origin could
498
+ silently reject true edge hits). Every model re-bakes once on your next
499
+ `looop dev`/`test`/`publish` to pick up the new artifact format; without a
500
+ `pivot` entry nothing changes in how it plays. Opting into a non-origin
501
+ pivot needs an engine that reads it (0.1.35+) — run `npx looop update`
502
+ first, or the room will place the model by its origin while the broadphase
503
+ is measured about the pivot.
504
+
505
+ ## [0.1.20] - 2026-07-29
506
+
507
+ ### Added
508
+
509
+ - **Generator scripts: `looop model bake` can run your code at bake time.**
510
+ Name a game-committed script in `<model>.bake.json`
511
+ (`{ "generate": "creature-gen.mjs" }`) and the bake hands it the parsed
512
+ model. Export `clips(model)` to bake a procedural pose function into
513
+ additive clips (sampled into delta tracks the engine's layer slots play —
514
+ see the engine changelog), and/or `zones(model)` to label triangles your
515
+ own way when bone names can't describe the mesh (one byte per triangle +
516
+ a name table). The script must be deterministic — the bake refuses
517
+ `Math.random()`-shaped output — and its text joins the staleness hash, so
518
+ editing the maths re-bakes automatically, exactly like editing the GLB.
519
+ The broadphase radius grows to contain every generated pose at weight 1.
520
+
521
+ ### Changed
522
+
523
+ - Baked artifacts re-bake once after this update (the artifact format version
524
+ advanced to carry generated clips). No action needed — the next
525
+ `looop dev`/`test`/`publish` does it.
526
+
527
+ ## [0.1.19] - 2026-07-22
528
+
529
+ ### Added
530
+
531
+ - **Your 3D models bake themselves.** `looop dev`, `looop test`, and
532
+ `looop publish` now notice when a GLB your entities reference has changed
533
+ (or its `<model>.bake.json` zone config has) and quietly regenerate the
534
+ server-side hit data — `<model>.skeleton.json` + `<model>.mesh.json`,
535
+ committed next to the model — so the server always shoots at the surface you
536
+ see. A model that can't bake stops the command with the reason and the fix
537
+ (a scale-carrying export, an unsupported fifth skin influence, a zone
538
+ pattern matching no bone), never a silently stale surface. Force a re-bake
539
+ or debug one with the new **`looop model bake <file.glb>`**. Baking parses
540
+ your GLB with the browser's own loader in a headless page — the exact code
541
+ that draws it — so what gets baked is what gets drawn. The loader ships
542
+ inside the engine, not with the `looop` command, so a game that never
543
+ touches a 3D model downloads nothing extra. Requires engine 0.1.32 or
544
+ newer; run `looop update` if your game pins an older one.
545
+
546
+ ## [0.1.18] - 2026-07-15
547
+
548
+ ### Added
549
+
550
+ - **`looop dev`, `looop test`, and `looop publish` understand entity
551
+ components.** A game that declares behavior in `components/` folders (the
552
+ entity system — see `shared/practices/entities.md` in your installed engine)
553
+ now works across the whole command surface: `dev` bundles your components
554
+ into the local room server, `test` runs the entity graph lint out of the
555
+ installed engine before anything else (it runs even when eslint isn't
556
+ installed in the game), and `publish` bundles them into your game's own
557
+ multiplayer server — a component game is server-backed automatically,
558
+ exactly like a primitives game. Requires engine 0.1.27 or newer; run
559
+ `looop update` if your game pins an older one.
560
+
561
+ ## [0.1.17] - 2026-07-14
562
+
563
+ ### Added
564
+ - **`looop test <pattern>` runs just the tests you name.** While you're iterating
565
+ on one part of your game, running the *whole* suite every time is most of the
566
+ wait. `looop test charge` now runs only the files whose name contains `charge`
567
+ (`charge.smoke.mjs`, `game-charge.test.mjs`) and skips the rest — a focused,
568
+ fast re-run. Name more than one (`looop test charge aim`) to widen it. It's for
569
+ the inner loop, not a replacement for the gate: a pattern that matches nothing
570
+ stops with an error (so a typo can never pass green having run nothing), and you
571
+ still run the full `looop test` before you call the work done — a change can
572
+ break a part you didn't touch, and only the whole suite catches that.
573
+
574
+ ### Fixed
575
+ - **`looop test` no longer pins your whole machine while smokes run.** A headless
576
+ browser has no graphics card, so a 3D game's tests were rendering on the CPU —
577
+ one test could quietly eat 8+ processor cores and freeze a modest laptop solid.
578
+ Tests now render on your actual GPU instead, which is what it's for: the same
579
+ test that hogged 8+ cores now uses a fraction of one, and renders several times
580
+ faster. Nothing in your game changes, and your tests check exactly what they
581
+ did before. On a machine with no usable GPU it simply falls back to the old
582
+ behaviour — never worse. (Set `LOOOP_TEST_NO_GPU=1` to force the old path.)
583
+
584
+ ## [0.1.16] - 2026-07-13
585
+
586
+ ### Added
587
+ - `looop lane <name>` — open an isolated copy of your game to experiment in. It
588
+ lives in its own folder, on its own branch, with its own dev stack, so an
589
+ experiment can't disturb the game you're in the middle of. Whatever you have
590
+ unsaved stays exactly where it is. Like the winner? Merge it. Don't? Delete
591
+ the folder. The lane borrows the engine from your main folder rather than
592
+ downloading it again, so opening one is close to instant. Your agent knows
593
+ when to offer this — ask it for `/worktree`.
594
+
595
+ ### Fixed
596
+ - **`looop publish` no longer guesses a name inside a lane.** Publishing claims
597
+ a name permanently, and the name defaults to your folder — so publishing from
598
+ a lane would have claimed `<game>-<lane>` forever for a throwaway experiment
599
+ (and, if your `package.json` sets `looop.slug`, would have overwritten your
600
+ live game with it). From a lane, publish now asks you to say the name out
601
+ loud: `--slug <name>`. Publishing your actual game is unchanged.
602
+ - **`looop dev` no longer kills a dev server that isn't its own.** It used to
603
+ take over ports 8000/1999/8788 unconditionally, so a second game — or a lane —
604
+ would shut down the game you already had running and steal its ports. Now it
605
+ steps around anything else on those ports and picks the next free set, telling
606
+ you which it took. Run as many games and lanes at once as you like; they no
607
+ longer fight. It still reclaims *its own* stale server, so you never end up
608
+ looking at yesterday's code.
609
+
610
+ ## [0.1.15] - 2026-07-13
611
+
612
+ ### Changed
613
+ - When a non-primitive file lands in your primitives folder, the error now sends
614
+ you to a `lib/` subfolder for the helper, instead of saying it can live
615
+ "anywhere in your game". It can't: a primitive runs in the browser too (to
616
+ predict with), so a helper outside the `/shared/` tree resolves on the room
617
+ server but 404s in the browser. `lib/` is reachable from both sides.
618
+
619
+ ## [0.1.14] - 2026-07-13
620
+
621
+ > How to write a room primitive — the folder, the filename-is-type rule, and
622
+ > subclassing an engine primitive — is a change to the engine's room library, so
623
+ > it lives in the engine changelog (`looop changelog`), not here. This file is
624
+ > only the `looop` command itself.
625
+
626
+ ### Changed
627
+ - `looop dev` and `looop publish` now **refuse an override the room server cannot
628
+ run**, naming the file and what to do instead. Copying a room internal
629
+ (`host-core.js`, `primitives/effects.js`, …) into `overrides/` used to be
630
+ accepted in silence and then simply never run on the server — your browser and
631
+ the room quietly ran different code. The only override the server honours is a
632
+ primitive, so that is now the only one the commands accept.
633
+
634
+ ### Fixed
635
+ - `looop dev` now runs **your game's own server primitives**. It always ran the
636
+ engine's stock room server instead, so a primitive you wrote worked once you
637
+ published but not on your own machine — which is the worst way round, because
638
+ you could only find the difference after shipping. `looop dev` and `looop
639
+ publish` now decide the same way whether your game has a server of its own.
640
+
641
+ ## [0.1.13] - 2026-07-12
642
+
643
+ ### Fixed
644
+ - **`looop update` deleted your engine.** The engine is not an npm package — it
645
+ installs without a record in `package.json`, so *any* `npm install` in your game
646
+ treats it as junk and removes it. `looop update` then updated the `looop` command
647
+ itself with an npm install — **after** putting the engine in place — and wiped it
648
+ out every time. The update said it succeeded; `node_modules/@looop-games/engine`
649
+ was gone. Everything that reads the engine then failed, and re-running
650
+ `looop update` cheerfully said "already up to date" and fixed nothing.
651
+
652
+ The engine is now the last thing installed, and an update that finds it missing
653
+ puts it back (from the local cache — instant, no download). If you hit this, one
654
+ `npx looop update` repairs it.
655
+ - `looop lint` said "this engine ships no rules yet" when the real problem was that
656
+ the engine was not installed at all — sending you to `looop update`, the one
657
+ command that could not help. It now tells you which of the two it is.
658
+
659
+ ## [0.1.12] - 2026-07-12
660
+
661
+ ### Added
662
+ - `looop lint` — check your game against the Looop rules: the mistakes that ship
663
+ a broken game while every test stays green. `looop lint --fix` applies the ones
664
+ that can be fixed mechanically.
665
+ - `looop test` now runs the lint first, before it starts a browser. A hardcoded
666
+ multiplayer host is reported in a second, instead of after a full smoke run
667
+ that passed for the wrong reason.
668
+
669
+ The rules themselves live in the engine (so `looop update` brings you new ones)
670
+ and your game's `eslint.config.js` imports the same file the gate runs — the
671
+ editor and the test can't disagree. Nothing changes for an existing game until
672
+ you install eslint (`npm i -D eslint`); new games get it out of the box.
673
+
674
+ ## [0.1.11] - 2026-07-12
675
+
676
+ ### Fixed
677
+ - `looop update` now updates the **`looop` command itself**, not just the engine.
678
+ It used to leave the command at whatever version your game had pinned — so an
679
+ update could hand you an engine whose skills tell you to run a command your
680
+ `looop` was too old to have (`Unknown command: changelog`). It now moves both,
681
+ and says which versions it moved you between.
682
+
683
+ If you are seeing `Unknown command` today, you are on a `looop` from before
684
+ this fix and it cannot update itself. Run this once, and it will keep itself
685
+ current from then on:
686
+
687
+ ```bash
688
+ npm update @looop-games/cli
689
+ ```
690
+
691
+ ## [0.1.10] - 2026-07-12
692
+
693
+ ### Added
694
+ - `looop changelog` — what changed in the engine. With no arguments it shows
695
+ every release newer than the version your game is pinned to; `looop changelog
696
+ <version>` shows one release, `--all` shows the whole history.
697
+ - `looop update` now prints the changelog for every version it crosses, with
698
+ **BREAKING** entries called out separately, so you find out what has to change
699
+ in your game at the moment you take the update — not when something breaks.
700
+
701
+ ## [0.1.9] - 2026-07-12
702
+
703
+ ### Changed
704
+ - `looop create` now signs you in and downloads the engine, which carries the
705
+ starter game and your game's skills. The login used to happen at your first
706
+ `looop dev`, one command later.
707
+ - The published package is now just the command itself. Everything you receive as
708
+ a creator — the starter game, the skills, the shared libraries — comes from the
709
+ engine, so the two can no longer drift apart.
710
+
711
+ ## [0.1.8] - 2026-07-12
712
+
713
+ ### Added
714
+ - `looop feedback` sends the files a report attaches, so whoever reads it can see
715
+ the code you are talking about.
716
+
717
+ ## [0.1.7] - 2026-07-11
718
+
719
+ ### Changed
720
+ - `looop update` refreshes your game's skills and `AGENTS.md`, and reports every
721
+ file it touched. A file you edited yourself is kept, not overwritten.
722
+
723
+ ## [0.1.6] - 2026-07-11
724
+
725
+ ### Added
726
+ - `looop update` — move a game onto the latest engine release and re-pin it.
727
+
728
+ ## [0.1.5] - 2026-07-11
729
+
730
+ ### Added
731
+ - `looop feedback` — send the reports under `notes/feedback/` to the Looop team.
732
+
733
+ ## [0.1.4] - 2026-07-11
734
+
735
+ ### Fixed
736
+ - `looop create` installs cleanly on macOS (`fsevents` is pre-approved along with
737
+ the other install scripts).
738
+
739
+ ## [0.1.3] - 2026-07-11
740
+
741
+ ### Fixed
742
+ - `looop create` works on Windows.
743
+
744
+ ## [0.1.2] - 2026-07-10
745
+
746
+ ### Added
747
+ - First public release. `looop create` scaffolds a game, `looop dev` runs it with
748
+ multiplayer, `looop publish` puts it on play.looop.games, `looop login`
749
+ authenticates the machine.