Files
flutter_lua_runtime/docs/lua-package-format.md

5.2 KiB

Lua game package format

A game package is a manifest plus Lua scripts and optional assets.

Typical structure:

assets/games/<gameId>/
  manifest.json
  scripts/
    main.lua
    state.lua
    ui.lua
    runtime_defs.lua
  assets/
    image.png
  audio/
    click.wav

Manifest modules

Lua modules must be declared in manifest.json.

{
  "id": "template",
  "version": "0.1.0",
  "entry": "main",
  "modules": {
    "main": "scripts/main.lua",
    "state": "scripts/state.lua",
    "ui": "scripts/ui.lua",
    "runtime_ui": "runtime:runtime_ui.lua",
    "runtime_widgets": "runtime:runtime_widgets.lua",
    "runtime_commands": "runtime:runtime_commands.lua",
    "layout": "runtime:layout.lua"
  }
}

Lua imports by module name:

local ui = runtime.import("ui")
local widgets = runtime.import("runtime_widgets")

Do not use require, package, dofile, loadfile, or os.

Path rules

Package-local module paths:

scripts/*.lua

Runtime shared module paths:

runtime:*.lua

runtime: paths must not contain /, .., or an empty filename.

Base packages

A game manifest can declare a base field to indicate it depends on a framework package:

{
  "gameId": "ludo",
  "base": "_framework",
  "modules": { ... }
}

The base field is metadata. Actual loading is controlled by RuntimeOptions.basePackages:

LuaGameWidget(
  gameId: 'ludo',
  runtimeOptions: const RuntimeOptions(
    basePackages: ['_framework'],
  ),
)

Loading order:

  1. Base packages are loaded first, in basePackages order.
  2. The game package is loaded last.
  3. All modules are merged into a flat map.
  4. Later packages override earlier packages on name collision.
  5. The entry script always comes from the last (game) package.

This means a game can override any framework module by declaring a module with the same name in its own manifest.

Multi-package module resolution

When Lua code calls runtime.import("xxx"):

  1. Look up xxx in the merged module map (game modules first, then framework).
  2. If not found, throw FormatException: Lua module is not declared in manifest.modules.

Framework modules are transparent to game code:

local app = runtime.import("app")       -- resolved from framework
local state = runtime.import("state")   -- resolved from game

Entry module

The manifest entry module should expose lifecycle/event functions expected by the script engine. Keep game-specific state in Lua modules and return runtime diffs/commands through the approved protocol.

Runtime helper modules

Shared helpers are provided by the runtime package:

assets/runtime/lua/runtime_ui.lua
assets/runtime/lua/runtime_widgets.lua
assets/runtime/lua/runtime_commands.lua
assets/runtime/lua/layout.lua

Use helpers for authoring convenience, but remember helpers must normalize into supported Runtime protocol nodes/commands before Dart validation.

Generated Lua definitions

runtime_defs.lua files are generated from common definitions. After changing helper APIs, run:

dart run tool/generate_lua_runtime_defs.dart

Check without rewriting:

dart run tool/generate_lua_runtime_defs.dart --check

Package sources

Host apps can load packages from three common sources:

// Bundled app assets.
AssetGamePackageRepository(runtimeOptions: runtimeOptions)

// Local development directory, useful when images should not be bundled into app assets.
FileGamePackageRepository(
  baseDirectory: 'E:/lua_packages',
  runtimeOptions: runtimeOptions,
)

// Remote update server.
RemoteGamePackageRepository(
  baseUri: Uri.parse('https://example.com/lua-packages/'),
  runtimeOptions: runtimeOptions,
)

A local development directory uses the same package layout as a downloaded remote zip:

E:/lua_packages/gomoku/
  manifest.json
  scripts/
  assets/

Remote compatibility

Remote manifests may include a compat block. The server should use request query values to return the newest compatible package, and the client validates the returned manifest again before download.

{
  "gameId": "gomoku",
  "version": "0.3.0",
  "packageUrl": "https://example.com/packages/gomoku-0.3.0.zip",
  "sha256": "...",
  "compat": {
    "runtimeApiVersion": 1,
    "minRuntimeVersion": "0.4.0",
    "maxRuntimeVersion": "0.4.9",
    "minHostBuild": 120,
    "platforms": ["windows", "android"],
    "channels": ["dev", "prod"]
  }
}

RemoteGamePackageRepository sends these query parameters when fetching remote_manifest.json:

runtimeApiVersion
runtimeVersion
hostBuild
platform
channel

Configure them through RuntimeOptions:

RuntimeOptions(
  runtimeVersion: '0.4.0',
  hostBuild: 120,
  platform: 'windows',
  channel: 'dev',
)

If compatibility fails, the remote package is not downloaded. The repository falls back to stable cache, previous stable cache, then bundled assets.

Package validation

A host repository can validate a game package with:

dart run tool/check_runtime_package.dart assets/games/template packages/flame_lua_runtime/assets/runtime/lua

Within this package repo, example game packages live under:

example/assets/games/