WurstScript compiles to either Jass or Lua. The goal is to write code once and have it behave identically on both backends — no conditional code paths, no per-backend workarounds in user space.
Set compilationTarget in your wurst.build file:
compilationTarget: lua
Jass is the default and requires no configuration. Lua targets Reforged and is required if you want to run on Battle.net with modern clients.
Jass and Lua have different runtime semantics in several areas. The most common differences that affect real maps:
Null handles. In Jass, calling a native with a null argument is valid — the function returns a type-appropriate zero value. In plain Lua, the same call raises a nil error and crashes.
Handle IDs. GetHandleId in Jass returns stable integers across a session, commonly used as table keys or for comparisons. In Lua, the equivalent is not stable across sessions and is a known cause of desyncs.
Uninitialized variables. Jass initializes all variables to zero values (0, 0.0, false, "", null). Lua leaves uninitialized variables as nil, causing type errors when first used.
To close these gaps, the compiler applies a dedicated Jass shim pass to all generated Lua code. This pass inserts targeted emulation so that Lua behaves like Jass for the cases above — without any changes needed in user code.
Null handle arguments return the same defaults as Jass instead of erroring:
| Expression | Jass | Plain Lua | Shimmed Lua |
|---|---|---|---|
GetUnitX(null) | 0.0 | nil error | 0.0 |
GetUnitState(null, UNIT_STATE_LIFE) | 0.0 | nil error | 0.0 |
IsUnitEnemy(null, null) | false | nil error | false |
GetUnitName(null) | "" | nil error | "" |
GetHandleId(null) | 0 | nil error | 0 |
This applies across the native surface — integer, real, boolean, and string return types all fall back to their Jass defaults when called with null handles.
All GetHandleId usage is replaced by the compiler with a safe alternative that produces consistent, desync-free identifiers. This is transparent — the replacement behaves the same way as the Jass GetHandleId for the purposes code actually relies on.
Wurst’s static type system enforces initialization for most cases at compile time. The shim pass handles any remaining gaps at the generated code level so zero-value semantics are consistent between backends.
Maps that run correctly on the Jass backend should run correctly on Lua after the shim pass. The typical process:
compilationTarget: lua in wurst.build.No changes to user Wurst code should be required for the cases described above.