This is the eighteenth and last post of a weekly build log for a 2D engine I am building solo. It does the inventory of what four and a half months actually produced, then withdraws the two claims from that log that turned out to be wrong.
Four and a half months of solo work produced an engine at 0.3.1 with 41 released packages, a game that runs end to end from boot screen to credits, seven playable browser demos, and seventeen weekly posts that never missed a Monday. It also produced two published claims I have to withdraw in full.
This solo game engine retrospective covers both. Between May and August 2026 I published a weekly build log for a 2D engine, its flagship game, and the tooling underneath both. Two of those posts describe a safety mechanism that has never existed in the codebase. One more published benchmark figures that a Windows power setting had quietly inflated.
Publishing a number every week is only worth doing if you also publish the week you were wrong. But a list of corrections is worth nothing on its own, so the inventory comes first, then the retractions, then the honest status of the game that was supposed to prove all of it.
A solo game engine retrospective needs an inventory first
Four repositories, four different jobs.
flare-engine is the engine: 41 scoped @flare-engine/* packages at 0.3.1, Apache-2.0 since 2026-07-12, and a test tree that is now bigger than the code it covers, 38,084 lines across 227 test files against 26,716 lines of package source. Every one of the 42 package directories has its own changelog, there are 199 release tags in the history, and no release is half-published: the changeset queue is empty. There is not a single TODO or FIXME left in the package sources.
What is inside those packages is not glue. A sequential-impulse rigid-body solver with pooled contact manifolds. A uniform spatial-hash broadphase. An archetype ECS that matches queries by bitmask instead of looking up components one at a time. A-star, Jump Point Search and a navmesh solver. Twenty-one hand-written SkSL shader programs, one of which packs 19 uniforms so that a six-stage post-processing chain runs in a single pass. Versioned save-migration chains, atlas bin-packing, a drop roll with a pity counter.
Twelve scripts totalling 2,392 lines guard all of it, and continuous integration runs thirteen checks on every push. One of those checks asserts exact allocation and draw-call counts, which means an unexplained improvement fails the build too, until someone sits down and re-baselines it on purpose.
Pan Tvardowski is the game, and it gets a section of its own further down, because its status is the thing this post most needs to settle. In scale: 34,190 lines across 310 files with 305 test files behind them, eighteen scenes, forty systems, and 517 localization keys at full English and Polish parity.
flare-engine-page is where the engine has to prove itself in public, and it is the piece I have most consistently undersold. Seven CanvasKit showcases are live at flareengine.dev/showcases, running in your browser rather than as video: a procedural cartography demo, a reactor simulation built on real reactor kinetics, a physarum agent field, a symplectic N-body spacetime lattice, a rigid-body sandbox, an animated logo on a hand-written specular shader, and Overrun, a playable sixty-second arena game running 14 engine packages in 3,283 lines. Behind those pages sit 13,691 lines of demo code, 5,215 lines of tests over them, and 11 pages of documentation. The build fails if a page claims a package it does not import, or imports one it does not claim.
grzott-portfolio is this site, and it is the only place the engine touches a real DOM. The page-collapse effect is 2,027 lines of implementation with 563 more in tests. It harvests laid-out elements into rigid bodies and drives them with CSS transforms, so the page keeps its accessibility tree and stays indexable the whole time it is falling apart. The admin half runs 22 dashboard pages and 7 API routes over 17 Supabase tables, and the pipeline behind these eighteen posts is another 8 agents, 13 commands and 22 scripts.
Not one of those four things is a shipped product, and the rest of this post is about that gap. But a gap is not an empty room, and every correction below is a correction to the record of work that exists.
Four clocks, and why the commit total is smaller than it looks
The headline number for 2026 so far is 2,654 commits, and the first thing to say about it is that 405 of them are counted twice. Pan Tvardowski's history before 2026-06-10 is flare-engine's history, carried across the split with the paths stripped and the SHAs rewritten, so both counts contain it. De-duplicated, the four repositories hold 2,249 distinct commits. Even that number comes from four codebases that started on four different days and are still moving at four different speeds.
# `git rev-list --count HEAD` in each repo, measured 2026-08-31
git -C flare-engine rev-list --count HEAD # 1,030
git -C pan-tvardowski rev-list --count HEAD # 889
git -C flare-engine-page rev-list --count HEAD # 266
git -C grzott-portfolio rev-list --count HEAD # 469 (main is 431)
# 1,030 + 889 + 266 + 469 = 2,654 - 405 shared across the split = 2,249 distinct
flare-engine started 2026-04-17. This site started three days later. Pan Tvardowski started two days after that, inside what was then the same monorepo, before it was split into its own repository on 2026-06-10. flare-engine-page did not exist until 2026-06-28, which means every showcase described above was built in about nine weeks. The blog is a fourth and slower clock on top of all of it: this is the eighteenth weekly post, seventeen weeks after the first one on 2026-05-04.
2026-04-17 flare-engine |========================================| 1,030 commits
2026-04-20 grzott-portfolio |=================================| 469 commits (branch; main 431)
2026-04-22 Pan Tvardowski |================================| 889 commits (405 shared pre-extraction)
2026-06-28 flare-engine-page |==========| 266 commits
2026-05-04 blog, post 1 ^ post 18 on 2026-08-31 (17 weeks later)
Collapsing all of that into "four months of work" is the trap. The engine's own bootstrap was a 12-day sprint that produced 303 commits before the project's first month closed, and April alone accounts for 390 of its 1,030. Averaging that spike across the whole window would misrepresent the sprint and the four slower months after it in one go, which is why nothing below quotes a rate.
The mechanism that never existed
Two corrections follow, and they are not the whole account. A June benchmark post published render figures roughly three times optimistic, along with a closing lesson about draw-call batching that the engine's own code outgrew in July. A May post carried the same figures. A July post called the engine MIT-licensed the day after it went Apache-2.0. I know about all three, each still needs a dated note on the live post, and none of them gets one here.
The first correction is not a number. It is a sentence, published twice.
The first post in this series, on 2026-05-04, said pnpm refuses to resolve an illegal import. The third, two weeks later, repeated it, calling pnpm "Gate 2 - the workspace resolver" and stating that a layering violation "fails at pnpm install." This is not a case of naming the wrong tool. The engine has never used pnpm, not once and not briefly: its very first commit declares Bun as the package manager, and no pnpm lockfile has ever existed in the history. Substituting the right name would not rescue the sentence either, because Bun installs hoisted. Every package resolves from the root node_modules, so there is no per-package boundary for any resolver to refuse. The mechanism is not misnamed. It is absent.
The second post is a landscape comparison and carries neither sentence, so it needs nothing.
What actually enforces the layer graph is a 270-line script in the verify gate. On every package it checks three things: dependencies declared but never imported, dependencies imported but never declared, and any package depending on something strictly above it in the graph, which breaks the downward-only rule the whole architecture is built on. Any violation fails the build.
Those two posts also gave different tier counts, and both were right on the day. It is six tiers deep now, measured as the longest path through the internal dependencies of all 41 packages, which form no cycles. The gate itself hand-assigns seven permission labels, and that is a different quantity from the depth. So the first post's five strict layers and the third post's seven-tier map were accurate snapshots when they ran, and neither is retro-corrected here. It was only ever the tool that needed correcting.
One more thing belongs in this correction, and it is the part that matters most. That script did not land until 2026-07-01. For the entire period those two posts describe, nothing mechanical held the layer rule at all. It held on briefings, on review, and on the rule being easy to follow. It held. I had the reason wrong, and the reason I gave was the one that made it sound automatic.
This is also the kind of error no fact-checking script catches, because it carries no number. It shipped as a tool name, not a metric. A table of facts can tell you a figure has gone stale; it has nothing to say about a claim that was never a figure. I now run a second check against a list of retired phrases, and that is the only thing that would have caught this one.
One withdrawal and one promise kept
The second correction is a number, and it needs to be scoped carefully, because it is one error and not two.
The CanvasKit web-performance post, on 2026-08-17, benchmarked CanvasKit's CPU-raster path against its GPU-raster path on an i9-13900KF with an RTX 4070 Ti, ANGLE/D3D11, chromium 151. Four days later a re-capture on the same machine found the CPU-raster figures inflated 2.44 times over. The cause was a Windows power-saving plan that had quietly dropped the display to a 30 Hz refresh rate. The harness derived its timings from the frame delta, so the degraded refresh and the inflated CPU cost were the same fact measured twice, and the capture format had nowhere to record which power plan was active. Nothing in the published numbers could have exposed that. It took an unrelated re-capture, four days later on a different power plan, to surface it.
That is the one undisclosed error in that post, and it is worth being blunt about what it is not. It is not the vsync ceiling the same post carried. That limit was stated in print at publish time: the GPU numbers were labeled an upper bound because the harness could not resolve timing below one display frame, and the post promised the fix in its closing section. The fix landed a day later. Resolving it did not create a second error. It replaced a disclosed ceiling with a measurement, and the measurement moved the honest headline in the post's favor.
| Metric (i9-13900KF + RTX 4070 Ti, ANGLE/D3D11, chromium 151) | Published 2026-08-17 | Re-captured 2026-08-21 |
|---|---|---|
| CPU raster, 1,000 sprites / 1 atlas | 2.44× too high | 10.750 ms (band 10.750-10.900, n=5) |
| Cold start, unthrottled | 3.2× too high | 57.5 ms |
| Warm start, unthrottled | 3.4× too high | 34.3 ms |
| Cold start, throttled 4G | held within 0.9% | 16,329.9 ms |
| GPU raster ceiling | disclosed upper bound, ≤ 0.642 ms | 0.1235 ms (band 0.1205-0.1256, n=5) |
| GPU raster vs. CPU raster | "at least 41×" | 87.1× |
One caveat on that right-hand column: it is the 2026-08-21 re-capture, the run that produced the correction. A later pass on 2026-08-27, against newer code, reads 11.250 ms on the CPU path against 0.132 ms on the GPU path, a ratio of 85.5 times. The correction stands. The absolute numbers keep moving with the code, which is why each of them carries a date.
The throttled-4G row is in that table on purpose. It is network-bound, so it barely moved under the corrected power plan, which makes it the control that says the fix was real rather than a wholesale re-baseline. The published "at least 41 times" was a genuine lower bound, correctly labeled as one, and it turned out to understate the real gap by more than double. So the count is one withdrawal, the CPU-raster figure, and one promise kept, the GPU ceiling resolving into a number instead of staying a caveat. The difference between an undisclosed error and a disclosed limit that later resolved is the whole reason to publish corrections at all.
The game runs. It is still not shipped.
The first post in this series promised two posts in August as proof points for Pan Tvardowski, the engine's flagship game. This is where I settle that, and the honest answer has two halves that keep getting collapsed into one.
The first half: the game works. There is an end-to-end path in code from the boot screen through menu, save select, hub, mission select and loadout into play, out through victory or game over, and back to the hub. Eight bosses run complete multi-phase state machines with desperation scaling and telegraph windups carved out of their own cooldowns. Sixteen enemy patterns, seven minibosses, eight zones crossed with ten difficulties into an eighty-cell unlock graph, sixteen HUD overlays, versioned save slots, entitlement gating that splits three free zones from five paid ones, and a post-processing chain confirmed at 60fps on a Galaxy A54 with five effects and four kinds of weather composited.
It is also not slowing down. The game carries 889 commits, 238 of them in August alone across eleven active days, and the busiest single day of the month saw 49. Last week's post cited its test suite at 2,286. It stands at 2,522 passing tests across 305 files today. That is 141 commits of growth between the two measurements rather than a correction of the earlier post: both figures were accurate when they were taken.
That count is its own suite. It does not add to the engine's, which separately ran 2,903 tests and passed 2,887 of them. The sixteen non-passes are a known quirk of the root test runner, and they pass when run standalone. I never sum the two, because they measure two different codebases.
The game declares 34 of the engine's 41 packages as dependencies and actually imports 32 of them across source and tests. Those are two different numbers, and both are worth keeping rather than picking whichever sounds better.
The clearest sign that it is past the "does it run" stage is the shape of the last playtest. A session on real hardware on 2026-08-26 turned up eight defects. Six were root-caused and fixed the same day; the other two were engine-side and shipped in 0.3.1. Boss pacing, dash feel, HUD gutters, the wording on locked rows. That is the defect list of a game being tuned, not a game being assembled.
Now the second half, and this is where I refuse to round up. Three things stand between the game and a store build, and only one of them is polish.
There is no audio content. There is an audio system, 269 lines of it, synthesizing six sound effects and one music track per zone at runtime, and there is not a single sample file in the repository. That is asset creation, not asset polish, and calling it polish would be exactly the kind of soft claim this post exists to retract. The art is Kenney stock, recolored on purpose rather than by neglect, and the app icon is still a placeholder.
Endless mode is in the launch scope and cannot be reached. The ruleset is built and unit-tested, but there is no entry point from the hub, so no player has ever run it. Three neighboring systems are dormant for the same reason: the bullet-curtain governor, auto-bomb and the revenge mechanic all need content that cannot currently be selected.
And there is a device-verification queue that is not balance tuning. The floating-joystick control scheme still breaks under two-finger input, because the fix that landed in 0.3.1 only covered the fixed scheme. A full presentation pass on hardware is outstanding. And I found a serious bug in passing last week: pausing, opening settings, then backing out wipes the run.
So Pan Tvardowski is still version 0.0.1. No production app bundle has been submitted, and it has never entered closed testing on either store. What does exist is the pipeline to get there: a production build profile targeting an Android app bundle, a submit config wired to the internal track, a written keystore procedure and three automated smoke flows. What does not exist is a paid Play developer account, and Google's closed-testing gate for personal accounts needs at least twelve testers opted in for fourteen continuous days before production unlocks. That clock has not started.
The target is a Google Play release in Q1 2027, and I am stating it the same way as the October dates below: a plan, not a promise. "The engine ships games" stays aspirational until a build reaches a store's testing track. But the unproven part of that sentence is distribution. It is not a claim about whether the game exists.
What actually ships on 20 October
The engine moved while all of this was being written. It sits at 0.3.1, published to the public npm registry with restricted access. The packages are genuinely on npm: an authenticated npm install resolves every one of them, and an anonymous one gets a 404. Published but not publicly installable is the distinction that matters, and both halves of it are easy to say misleadingly on their own.
0.3.1 is not the launch cut. The plan freezes on 2026-10-05 and publishes 0.4.0, still restricted, at the freeze. October flips that version from restricted to public. The license has been Apache-2.0 since 2026-07-12, across all 41 scoped packages.
Two dates matter for closing the gap, and both are plan targets rather than guarantees: a feature freeze on Monday 2026-10-05, and the flip from restricted to public on Tuesday 2026-10-20. Until the second one, there is no install command worth printing, and none of the numbers in this post change that.
Limits
- Four repositories, four start dates, and 405 commits counted twice. 2,654 is the naive sum; the de-duplicated union is 2,249, because Pan Tvardowski carried flare-engine's history across the June split. The spans are not comparable either: flare-engine-page is about nine weeks old against flare-engine's nineteen, so averaging activity across the two would misrepresent both.
-
This site's share of that total is a branch count, not
main's. 469 commits is measured on the working branch;mainsits at 431. The two differ by 38, and the sum above uses the larger one. - Commit counts measure activity, not value, and they measure directed agents rather than hand-typed lines. This was built by directing AI agents, as the first post in this series said at the time. A 12-day, 303-commit bootstrap and a steadier 103-commit August are not comparable units of work, which is why no rate appears anywhere above.
- The inventory is a snapshot, not a steady state. Every line count, package count and test count here was measured on 2026-08-30 or 2026-08-31, and several of them moved during the week I was writing. Expect them to have drifted by the time you read this.
- Both corrections are self-reported. Nobody caught either error from outside. The pnpm mechanism and the power-plan inflation both surfaced because I re-ran measurements I already owned, which is weaker evidence of rigor than "somebody audited my work", and I would rather say so than leave it for a comment thread.
-
Almost none of this is something you can run. The engine's npm scope is restricted, so an anonymous install returns a 404, and will until the October flip. Pan Tvardowski is
0.0.1with no submitted app bundle and has never entered closed testing. The seven browser showcases are the only part of any of this you can use today. - 20 October and Q1 2027 are plan targets, not shipped dates. Freeze, flip and store dates can move. This is the current plan, not a guarantee.
Close
Eighteen weeks produced an engine at 0.3.1 with 41 released packages, a game that runs from boot screen to credits and is being tuned rather than assembled, seven CanvasKit showcases anyone can open in a browser, and a weekly publishing record that did not miss a Monday. It also produced two claims that had to be withdrawn in public, one of which had been sitting in the very first post since May.
Those are not two separate stories. The inventory is worth printing because the retractions are printed next to it, and the retractions are worth printing because there is something real to be wrong about. If there is one practice worth copying out of any of this, it is not the weekly cadence and it is not the package count. It is publishing the correction as its own post, with the wrong number still visible beside the right one, instead of quietly editing the old one and moving on. The RSS feed at grzegorzotto.dev/blog/rss.xml is how you catch the next one.
Originally posted on grzegorzotto.dev.