Lua Gameplay on a Fixed-Memory PS1 Runtime
Lua is valuable in a PS1 engine because it separates gameplay iteration from C runtime work. Lua is also dangerous on a PS1 if it grows without limits.
Polygon Engine's scripting model is built around that tension: scripts should be easy to attach, inspect, and test, while the runtime keeps memory, script count, and API surface bounded.
What Lua Owns
In Polygon Engine, Lua is responsible for gameplay behavior:
- responding to triggers and collisions,
- driving pickups, gates, terminals, and stations,
- updating UI text and focus,
- calling scene loads,
- playing audio sources,
- toggling GameObjects,
- reading and writing small save data values.
Lua does not own the renderer, asset loader, SPU upload path, mesh format, or build pipeline. Those stay in C/C++.
That split is deliberate. The PS1 runtime should expose a small set of stable APIs instead of letting scripts reach into every subsystem.
GameObject-Attached Scripts
Scripts are attached to scene GameObjects through a Lua Behaviour component. The runtime invokes lifecycle functions when they exist:
function Start(gameObjectId)
Log.Print(GameObject.GetName(gameObjectId) .. " ready")
end
function Update(gameObjectId, deltaTime)
Transform.RotateY(gameObjectId, 45 * deltaTime)
end
function OnTriggerEnter(gameObjectId, otherGameObjectId)
Log.Print("triggered by " .. tostring(otherGameObjectId))
end
The engine also supports script-to-script style calls through
GameObject.Invoke and UI Button callback targets. Build/editor validation can
detect missing callback functions before the runtime is launched.
Runtime Scripting Limits
The current scripting limits are intentionally visible:
| Limit | Value |
|---|---|
| Max script components | 32 |
| Max unique script sources | 32 |
| Max properties per script component | 8 |
| Max script property name | 32 bytes |
| Max script property value | 64 bytes |
| Max Lua heap | 512 KB |
| Max individual script size | 64 KB |
The default project can choose smaller settings, but it cannot exceed the runtime cap. The build validates those caps.
Script Properties
Script properties are one of the most useful workflow features. They let the same Lua file behave differently per GameObject.
For example, a station script can expose:
stationId = 3
promptText = "Activate"
targetGameObjectId = -1
The editor reads defaults, lets the user override values in the Inspector, and stages those values with the scene. The runtime passes them to the script component.
This is the kind of feature that makes a PS1 editor practical: fewer duplicated scripts, more data-driven GameObject behavior.
Instruction Budget
Runaway scripts are especially harmful on a fixed runtime. A bad loop can steal the entire frame and make the game look like a renderer problem.
Polygon Engine exposes an instruction budget setting. When enabled, Lua calls are guarded so runaway scripts can be stopped instead of silently destroying frame time.
The build also warns when the instruction hook interval is larger than the per-call budget, because that configuration lets scripts overshoot before being interrupted.
Authored UI, Not Just Debug Text
The runtime supports up to eight named UI canvases with independent reference sizes, visibility, and sort order. The fixed scene-wide element budget covers:
- Panel,
- Text,
- Image,
- Button,
- ProgressBar.
Lua can find UI elements, change text, progress, position, size, and color, toggle element or canvas visibility, change canvas order, and set or read focus:
local progressText = -1
local hudCanvas = -1
function Start(gameObjectId)
hudCanvas = Canvas.Find("HUD")
Canvas.SetEnabled(hudCanvas, true)
progressText = UI.Find("Progress Text")
Text.SetText(progressText, "Stations 0/5")
end
function Update(gameObjectId, deltaTime)
if Input.GetButtonDown(Input.CROSS) then
Text.SetText(progressText, "Activated")
end
end
This is different from drawing temporary debug text. The authored UI is part of the scene data and is staged for the runtime.
Save Data
The SaveData API is a bounded key-value layer backed by one 8,192-byte memory-card block:
if SaveData.Write("checkpoint", "hub") and
SaveData.Write("station_03_complete", "1") then
if not SaveData.Flush() then
Log.Print(SaveData.GetLastError())
end
end
local checkpoint = SaveData.Read("checkpoint")
The runtime supports 32 entries, with 31-character keys and 63-character values. The file includes a standard memory-card header, project-defined title, 16-color icon, versioned payload, and checksum.
The API reports card absence, capacity errors, invalid values, incomplete writes, corrupt payloads, and unsupported versions. Gameplay code can therefore show a real save result instead of assuming that flush() succeeded.
Supported older save payloads are decoded and rewritten in the current format after the next successful flush. Migration stays inside the persistence layer; gameplay scripts continue to read the same keys.
The system is intentionally small. It covers checkpoints, settings, progress flags, and compact player state without turning the memory card into a general database.
Why This Approach Fits PS1
The PS1 does not have room for an unbounded scripting environment. The engine has to make Lua feel friendly while keeping it measurable.
The rule is:
Lua owns game rules.
C runtime owns hardware.
Editor owns validation.
Build owns packaging.
That separation is what makes Lua practical for PS1 homebrew instead of just impressive in a screenshot.
Continue Reading
Saving and testing your game
Use PlayerPrefs for typed progress and settings. Call PlayerPrefs.Save()
to persist changes and SaveData.Reload() to load the last save. Check each
result before displaying a success message. Keep keys short: PlayerPrefs
allows 28 bytes per key and 61 bytes per string value.
Test save, load, reset, and retry with View > Save Data Simulator, then repeat those flows in your packaged game. See the Persistence reference and Two-Room Relay tutorial.
For script errors, Debug builds retain source line information. Keep Lua memory and instruction limits in mind and measure gameplay performance; see the Runtime Reference.
Updated for Polygon Engine 0.1.0-ea.2.