Parallel by architecture
Every file is processed across all cores with rayon. darklua is single threaded and has been since its parallelize issue was filed in 2021.

One parallel Rust binary. Requires that cannot break, style that cannot drift, and worms that teach it languages beyond Luau. It starts with the require rewriting nothing else in the ecosystem does.
Parallelism, one parse per file, a constant number of transform passes, and incremental builds. Each one is a multiplier that darklua can't retrofit.
Every file is processed across all cores with rayon. darklua is single threaded and has been since its parallelize issue was filed in 2021.
Rewrites are byte range splices into the original source, so untouched bytes are never printed again. No second AST either.
Expand @alias imports into require("@game/...") paths the Roblox engine understands natively. No Instance chains, output stays diff-able.
check catches requires that pass on disk but fail in a running game: server/client realm violations, Starter-container clones, and wait semantics.
The cache key covers the whole resolution environment, not just file bytes, so it is never stale. Watch mode keeps the last good output when a save fails to lex.
default.project.json stays the source of truth and larvae derives the build project from it, instead of the two hand synced files darklua needs.
Roblox ships native string requires but no custom aliases. larvae expands your aliases at build time, and no other tool can rewrite into that form.
config
# larvae.toml [aliases] pkg = '@game/ReplicatedStorage/Packages'
Aliases merge over your .luaurc, so projects that already define them there need no config at all.
const Signal = require('@pkg/signal') const Config = require('./config') const Util = require('@self/util')
You write clean alias imports, backed by larvae.toml or your .luaurc.
These are structural differences. The numbers below are the receipts.
The headline is the matched comparison, both tools cold on the same tree running the same 10 rules. 16 cores, 7 runs per cell, median reported, larvae 0.1.0 against darklua 0.19.0.
the three workloads
synthetic project
same rules, both cold
synthetic project
same rules, both cold
single 3.5 MB module
same rules, both cold
Rules cost far more here than require rewriting does, 140 ms against 16 ms, because one enormous module gives the thread pool nothing to split. It is the workload larvae wins by the smallest margin once rules are on.
incremental, ours alone
darklua has no cache, so every darklua run is a cold run and these columns have no counterpart.
| files | cold | warm | one edit | check |
|---|---|---|---|---|
| 3,000 files | 24 ms | 14 ms | 13 ms | 21 ms |
| 5,000 files | 40 ms | 22 ms | 22 ms | 37 ms |
| 1 large file | 16 ms | 3 ms | 3 ms | 80 ms |
the linter, against selene
matched holds both tools to the 30 lints both implement, defaults runs what each enables: 49 lints for larvae, 33 for selene. 16 cores, median of 7.
matched, both lint sets equal
| files | workload | larvae | selene | speedup |
|---|---|---|---|---|
| 3,000 files | matched | 22 ms | 121 ms | 5.5x |
| 3,000 files | defaults | 26 ms | 121 ms | 4.6x |
| 5,000 files | matched | 34 ms | 201 ms | 5.9x |
| 5,000 files | defaults | 37 ms | 202 ms | 5.4x |
One 1 MB module lands at 279x, and it is deliberately not the headline: selene’s cost per byte climbs with file size while larvae stays linear, so that row measures parser throughput, not a build. The matched row also favours selene, it holds 3 of selene’s lints off against 19 of larvae’s, so read the 5.5–5.9x as a floor on the gap.
On the matched rows larvae is also doing require rewriting that darklua is not. darklua can convert requires from a rojo sourcemap, but that needs a separate rojo run, so it is left off rather than timed unfairly. No speedup is claimed against darklua’s default stack, because that run is not matched rule for rule.
lpm tool add larvae # or lpx larvae # or cargo install larvae
Three separate routes to the same binary, not commands to run in order. lpm adds it as a project tool, lpx runs it once and installs nothing, and cargo pulls the published crate. Prebuilt binaries ship on GitHub releases.
larvae initWrites a starter larvae.toml with every fmt option and lint at its default as comments, offers to add .larvae/ and dist/ to your .gitignore, and turns Luau's own lints off so nothing reports twice.
larvae process rojo serve .larvae/build.project.json
larvae writes dist/ and keeps the derived project file fresh, then you point rojo at it. It never runs rojo for you, serving is rojo's job.
the full toolkit
larvae process # rewrite requires, run rules; -w rebuilds on change larvae check # the whole-project analysis, the CI gate larvae fmt # format in place, stylua parity larvae lint # 51 lints with selene's spellings larvae bundle # the project as one file, modules load on demand larvae lsp # diagnostics and formatting for the editor larvae init # write a starter larvae.toml larvae worm # develop an extension larvae self # install, update, uninstall, code