I'm mid-port on two things right now: a Pascal solver called YASS, going to Rust, for the Sokoban work on tilebasedworlds.com, and BurntSushi's fst crate, going from Rust to Nim, for the search layer behind nlpcitations.com. Different source languages, different target languages, different domains — but the same failure mode kept showing up when I tried to hand either of them to a model in one shot. Paste in a few thousand lines, ask for a port, get back something that compiles and is subtly wrong in ways that take longer to find than writing it yourself would have. The fix wasn't a better prompt. It was splitting the work into stages that don't all deserve the same model.
I've been building a Sokoban editor and solver for tilebasedworlds.com — a side project for tile-based puzzle tools. The solver ships entirely in the browser as a WASM binary, which meant the first real question was not "how do I implement A*?" but "how do I represent a level?" That choice turns out to propagate through every part of the solver, the URL format, and the state-space design. The answer I landed on was bitplanes.
In the previous article I described a path from dynamic languages toward TypeScript, and from TypeScript toward stricter compiled languages. Rust is the end of that path — but it took some convincing to get there, and the thing that convinced me wasn't the systems programming story. It was the frontend.
The TypeScript Ceiling
TypeScript is an extraordinary tool within its constraints. The type system is expressive, the ecosystem is vast, and the developer experience — particularly with modern tooling — is genuinely good. But TypeScript erases at runtime. The types you write are a compile-time overlay on JavaScript; they do not exist when the code executes. The compiler can tell you when your model is inconsistent, but it cannot prevent every class of runtime failure.