u/reifel1

▲ 0 r/lua

TL;DR: I've been writing Lua for years across two very different runtimes: Project Zomboid mods (Wheelbarrow, Firetrail, Dragon Radar w/ 130k+ Workshop subs combined, top mod alone broke 140k subscribers) and now a full browser arcade game in Defold (WASM-compiled Lua 5.1, HTML5 export). Sharing what i learned about Lua specifically: the gotchas, the patterns that scaled, and the surprises going from "modding inside someone else's runtime" to "shipping a full game from scratch."

SOLONE by reifel

More casual gaming reddit


Hey Reifel here. This will be very technical

Quick background since it shapes the rest of this post: i spent the last few years writing Lua mods for Project Zomboid. PZ exposes a Java-side game engine to Lua hooks — you write Lua that gets injected into the running game and called via event callbacks. My most-used mod broke 140k subscribers on the Steam Workshop, and the combined catalog (Wheelbarrow, Firetrail, Dragon Radar) is past 130k. It taught me Lua at a depth no tutorial would have: hot-patching live games, debugging across a JNI boundary, working in a heavily sandboxed runtime where half the standard library is missing.

Then i learned that Defold also runs on Lua, and i wanted to test whether the same muscle memory could carry me from "modding someone else's engine" to "shipping a full game." The result is SOLONE: a 30-second arcade game running entirely in HTML5 (Lua compiled to WASM under the hood), with online leaderboard, opens in any browser tab.

What surprised me most: the two runtimes share Lua 5.1 as the language but diverge wildly in everything else. PZ has a forgiving sandbox with rich Java interop and global event hooks. Defold has a strict component model with message-passing-only IPC. Same pairs, same metatables, same coroutines — completely different mental model around state and communication.

Below are the Lua-specific things that bit me, since this is the part this sub actually cares about.

Lua 5.1 in production, the parts that hurt

Both PZ and Defold run Lua 5.1, which means no goto/::label::, no integer division //, no bitwise operators outside the bit library, and no continue in loops. After a few years you stop missing them, but the patterns still bite when you forget:

-- WRONG (5.2+ thinking)

for k, v in pairs(t) do

if bad(v) then goto continue end

use(v)

::continue::

end

-- CORRECT (5.1)

for k, v in pairs(t) do

if not bad(v) then use(v) end

end

In PZ this is event-driven approach because most of your code lives inside event hooks (short functions). In Defold with a 60-frame-per-second update loop on every game object, the lack of continue shapes how you write entire systems.

The bug that ate three days: zero and false silently dropped from messages

Defold serializes inter-script messages with Protocol Buffers. Fields whose value is 0, 0.0, or false get DROPPED at the protobuf layer because they match the default value. The receiver gets nil, not the value you sent.

-- sender

msg.post(url, "config", { strategy = 0, enabled = false })

-- receiver

function on_message(self, message_id, message, sender)

print(message.strategy) -- nil, not 0

print(message.enabled) -- nil, not false

end

Fix on the receiver side is the or fallback pattern:

self.strategy = message.strategy or 0

PZ doesn't have this problem because mod-to-mod communication is direct Lua function calls through a global table. But it has its own version of the gotcha: PZ globals can be nil, false, or missing-entirely, and you need three different checks (type(x) == nil doesn't exist in 5.1, so you write x == nil, x == false, and not x depending on intent). Same lesson, different runtime: never trust truthy checks across serialization or IPC boundaries.

This same Defold bug bit my telemetry pipeline: i was sending arrays of intra-run kill counts per 5-second window, and every zero (idle window) was disappearing. Fix: json.encode the array before sending, json.decode on the server. The protobuf layer treats it as one opaque string, zeros survive.

Module-level state is SHARED across instances (classic Lua singleton trap)

Defold runs with shared_state = 1, meaning ONE Lua state for the whole game. Every .script file's module-level locals are shared across every instance of that script. So:

-- BROKEN: shared across all FPS display instances

local samples = {}

local sum = 0

function update(self, dt)

sum = sum + dt

end

-- CORRECT: per-instance state on self

function init(self)

self.samples = {}

self.sum = 0

end

function update(self, dt)

self.sum = self.sum + dt

end

The bug only shows when you have 2+ instances, which is why it survived months in my code before i caught it.

PZ taught me the inverse lesson: there everything is global by default and you fight to keep state encapsulated. In Defold, scripts feel like classes but the module body is shared. Coming from PZ i instinctively scoped state per-instance from day one — that habit saved me a lot of debugging.

vmath.normalize NaN crash

Defold's vector math has no zero-length guard on normalize. Pass it the zero vector and you get NaN propagating through your physics state. Wrapper that saved my sanity:

local function safe_normalize(v)

local len = vmath.length(v)

if len < 0.001 then return nil end

return v * (1 / len)

end

Then every caller does if dir then ... end instead of trusting the output blindly.

GO IDs as Lua table keys need tostring()

Defold's runtime IDs are numeric hashes. Using them directly as Lua table keys can collide on hash-to-integer conversion. Across 5+ months of building i never reproduced the collision, but the official guidance is firm:

self.enemies[tostring(id)] = id -- always tostring the key

PZ has a similar gotcha with Java object references as keys (the tostring of a Java object includes its hash code, but two different objects can stringify to the same prefix in some PZ builds). Different language interop, same defensive pattern.

What pleasantly surprised me about Lua at this scale

A few patterns that just worked across BOTH runtimes:

  • Coroutines for sequenced flows. In PZ for multi-step crafting timers ("pour gas, wait, ignite, propagate"), in Defold for animation sequencing ("fade in, hold, fade out, delete"). Cleaner than callback chains in either runtime.

  • Tables as ring buffers. 30-frame FPS averager with one table and a rolling index, zero allocations per frame. Same trick i use in PZ for tracking damage-over-time effects.

  • Pure stateless helper modules. local M = {} + return M pattern works identically in both runtimes. My vmath helpers, color palettes, and AI strategy lookups port between projects with no changes.

  • html5.run() as a Lua-to-JS bridge. Defold-only, but worth highlighting: i call browser APIs (PWA install prompt, navigator.share, soft keyboard handshake on iOS Safari) directly from Lua via string-encoded JS. Surprisingly clean for what could have been an FFI nightmare.

  • Hot reload everywhere. Both PZ (via /reloadlua) and Defold (save and the engine reloads scripts live) made tuning constants almost zero-friction. This is one Lua advantage i don't think gets talked about enough — when your game logic is a script, balance work happens in seconds instead of recompiles.

Try it (Lua running in your browser via WASM)

If anyone here has shipped Lua across runtimes (Love2D, Defold, PICO-8, World of Warcraft AddOns, OpenResty, modded Minecraft, Roblox-style sandboxes, Project Zomboid), i'd love to compare notes on:

  1. State isolation when the host runtime shares one Lua state across many "instances"

  2. Serialization layers that drop falsy values (the protobuf zero-drop is not unique to Defold)

  3. Debugging Lua across language boundaries (JNI, WASM, FFI), where stack traces lose source maps

My Lua catalog so far (140.000+ users)

  • Wheelbarrow (PZ, 140k+ subs) to carry logs and heavy items while building

  • Firetrail (PZ) to pour gasoline in a line and ignite, used for corpse cleanup

  • Dragon Radar (PZ) to locate wanted items on the map

  • PZRank leaderboard (PZ + web) for community survival ranking, mod writes scores via Lua, a small Node service aggregates them

  • Workshop stats dashboard (web) for Lua mod metrics scraped and visualized

  • SOLONE (Defold, all-Lua) the current project this post is about

Happy to dive into any specific Lua/Defold/PZ-modding question in the comments. Discord is @reifel1 (server) for shop talk.

reddit.com
u/reifel1 — 12 days ago

EDIT: The first version of this post had a clumsy title ("a girl just beat me in my own game") that didn't land well. That's on me. The point was always the leaderboard, not who was on it. I rewrote the post below. Thanks to the people who flagged it kindly, and to the ones who didn't, i hear you too.


TL;DR: I'm a solo dev who came up through Project Zomboid modding (around 130k combined Workshop subs, Wheelbarrow + Firetrail ). All in Lua. Recently used the same Lua experience to ship my first solo full game, a small browser arcade called SOLONE. Posting this hoping a few of you give it a shot and tell me what you actually think.


Hi, i'm Reifel.

Some of you might know me from Project Zomboid modding, where i built Wheelbarrow, Firetrail, and Dragon Radar over the last few years. A few of you might also remember the PZRank leaderboard project i shared a while back. All of those are written in Lua, which i learned the hard way by reading other people's mod source code and breaking things until they worked.

Defold (the engine i used for this new project) also runs on Lua. When i found that out, i figured i'd see if the same skills i used for modding could carry me through shipping a standalone game from scratch. The result is SOLONE, a 30 second arcade browser game with an online leaderboard, no install, opens right in your tab.

I'm putting a lot of myself into this. It's my first solo game, so i'm still learning what works and what doesn't, and feedback genuinely helps me improve it. The honest hook for posting today: my own friends are beating me on hard mode. One hit 329 kills in 276 seconds, another hit 211 in 188 seconds. I can't catch either of them. I wanted to see if anyone here could humble me further, or maybe let me reclaim my own game.

Play in your browser

A few things about the game

  • No install, runs in any browser, no account needed
  • Pause menu has a poll where you vote on which power ups ship in the next patch (your vote actually counts, i read every result)
  • Solo dev, current version v1.8.97, constant updates
  • Behind the scenes it's actively keeping your FPS stable while you play, even on cheaper devices. Stress tests pass cleanly. That part you'll never see, which is exactly the point.
  • Short gameplay clip if you want a preview: TikTok

My PZ projects (in case you're curious)


If you're a designer or dev who'd like to collaborate on future versions, reach me on Discord: @reifel1 (server invite).

Honest feedback is welcome. So are kind words, since they fuel the next update. If you give it a shot, drop your high score below. Bonus respect if you beat me on hard mode.

u/reifel1 — 12 days ago
▲ 7 r/lua+2 crossposts

EDIT: The first version of this post had a clumsy title ("a girl just beat me in my own game") that didn't land well. That's on me. The point was always the leaderboard, not who was on it. I rewrote the post below. Thanks to the people who flagged it kindly, and to the ones who didn't, i hear you too.


TL;DR: I'm a solo dev who came up through Project Zomboid modding (around 130k combined Workshop subs, with Wheelbarrow alone past 140k). All in Lua. Recently used the same Lua experience to ship my first solo full game, a small browser arcade called SOLONE. Posting this hoping a few of you give it a shot and tell me what you actually think.


Hi, i'm Reifel.

Some of you might know me from Project Zomboid modding, where i built Wheelbarrow, Firetrail, and Dragon Radar over the last few years. A few of you might also remember the PZRank leaderboard project i shared a while back. All of those are written in Lua, which i learned the hard way by reading other people's mod source code and breaking things until they worked.

Defold (the engine i used for this new project) also runs on Lua. When i found that out, i figured i'd see if the same skills i used for modding could carry me through shipping a standalone game from scratch. The result is SOLONE, a 30 second arcade browser game with an online leaderboard, no install, opens right in your tab.

I'm putting a lot of myself into this. It's my first solo game, so i'm still learning what works and what doesn't, and feedback genuinely helps me improve it. The honest hook for posting today: my own friends are beating me on hard mode. One hit 329 kills in 276 seconds, another hit 211 in 188 seconds. I can't catch either of them. I wanted to see if anyone here could humble me further, or maybe let me reclaim my own game.

Play in your browser

A few things about the game

  • No install, runs in any browser, no account needed
  • Pause menu has a poll where you vote on which power ups ship in the next patch (your vote actually counts, i read every result)
  • Solo dev, current version v1.8.97, constant updates
  • Behind the scenes it's actively keeping your FPS stable while you play, even on cheaper devices. Stress tests pass cleanly. That part you'll never see, which is exactly the point.
  • Short gameplay clip if you want a preview: TikTok

My PZ projects (in case you're curious)


If you're a designer or dev who'd like to collaborate on future versions, reach me on Discord: @reifel1 (server invite).

Honest feedback is welcome. So are kind words, since they fuel the next update. If you give it a shot, drop your high score below. Bonus respect if you beat me on hard mode.

u/reifel1 — 12 days ago