writing
Notes from the studio
Short posts on the small, specific lessons that come out of shipping indie apps. Usually one a week, usually pulled out of that week's commits.
-
The AI training graveyard: CMA-ES, REINFORCE, a phased linear model, and the 28 weights that survived
Two days in March, five rewritten training pipelines. MCTS lived in the repo for ninety minutes. A phased linear model with 832 parameters got deleted in one commit. The only direct ancestor of a shipping weight file is a 28-weight switching-linear model that took its lessons from everything that failed.
-
The AlphaZero detour: batched MCTS, three value targets, and a sign bug that hid in plain sight
A month on AlphaZero for a mobile game that ships one forward pass per move instead. Virtual-loss batched MCTS, three value-target formulations in six hours, a PUCT sign bug that burned two days, and why the engineering survived even though the weights didn't.
-
A 403 from Cloudflare R2 on boot, and why I ended up bundling everything in the Docker image
Cloudflare's bot protection blocks Python's default urllib User-Agent with a 403. Here's the one-line fix — and why I later deleted the download path entirely.
-
Training per-opponent hint policies with CMA-ES (and why we deleted the whole system)
An in-game hint isn't 'what's the best move.' It's 'what move gives the player the highest win rate against this specific opponent from here.' That observation got us into twenty separate CMA-ES trainings. Then the main model got strong enough, and we threw all of it out.
-
Drawing connected cells as one outline: contour paths, inset normals, and a bridge animation
Replacing per-cell rounded rectangles with one SVG contour path per connected component in a Skia React Native game board. A right-hand-rule walker over a vertex grid, inset padding via right-hand normals, convex-only corner rounding with a clamp, and the 300ms bridge animation that hides the fact that the contour on frame N and the contour on frame N+1 share no structure.
-
Proving your difficulty ladder is real: round-robin heatmaps, pinned seeds, and the Blended Hard probe
A four-tier AI difficulty selector makes a monotone-progress promise to players. Here's the round-robin tournament, the two heatmaps, and the Math.random() seed bug that turn that promise from marketing into a reproducible assertion.
-
Four difficulty tiers, two model families: 14 features, 13 weights, and a distilled CNN
Cell Division ships Easy, Medium, Hard, and Elite. The first three are linear models over 14 hand-crafted features trained with PPO self-play; Elite is a distilled AlphaZero student. Here's why that combination — not just the CNN — is the right shape for a graduated-difficulty game.
-
One perf bug is three bugs: a remounting SVG, un-memoized cells, and 2 MB sprite decodes on iPhone 11
Why [JellySplit](https://jellysplit.com) stuttered on iPhone 11 and not on newer devices or the simulator. A React key that accidentally depended on every move, an unwrapped component that threw away its native sprite view on every swap, and a 2 MB per-sprite decoded-bitmap footprint that started evicting the cache under sustained play. Three fixes stacked — none alone was enough.
-
One AI, four board sizes: feature normalization, a 10×10 padded CNN, and entropy-timed pacing
The Jelmata AI runs on phones with no GPU. A single linear model serves four board sizes, a distilled CNN handles the top tier with a switching-linear safety net for web builds, and a one-line entropy trick makes a sub-millisecond move feel like a considered one.
-
Shipping a CNN game AI on-device in Expo with ONNX (the linking, the testing, the config plugin)
A 303 KB distilled AlphaZero lives inside two of my iOS/Android games. Here's the three-part Expo integration that wasn't in any tutorial: the autolinking hole, the Jest sandbox workaround, and the model pipeline.
-
Solving every level at build time: how 1,532 hand-crafted puzzles stay winnable
A memoized DFS solver runs over every puzzle on each build, precomputes the hint table, then a test simulates playing each one to confirm a 3-star win. Here's how it works — and the seven puzzles it killed.
-
Why I swapped custom screenshots for Apple's App Store preview videos
App Store previews are already the best footage of your iOS app. Here's how I pulled them into my landing page — and the one gotcha that bit me.