ScrapComputers Emulation Tool
Project description
EmuSC
EmuSC is a desktop emulator for ScrapComputers, a Lua computer mod for Scrap Mechanic. It lets you run and test ScrapComputers scripts on your machine without launching the game.
Scripts run against simulated hardware: displays render in a window, a terminal handles text I/O, keyboard input is captured, motor physics are ticked, and so on. The Lua environment exposes the same API the mod provides in-game - those are documented at scrapcomputers.dev/docs. This README covers the emulator itself: how to run it, how to declare hardware, and where it deviates from the game.
Installation
pip install EmuSC
Requires Python 3.10 or newer.
Usage
python -m EmuSC <script.lua> [options]
Options:
| Flag | Description |
|---|---|
-H SPEC, --hardware SPEC |
Attach a hardware component (repeatable) |
-c FILE, --config FILE |
Load hardware configuration from a JSON file |
-d DIR, --data-dir DIR |
Directory for persistent hardware data (default: .) |
-t HZ, --tick-rate HZ |
Simulation ticks per second (default: 40) |
--dev |
Echo Lua print() output to stdout in addition to the terminal window |
-D, --debug |
Open the debugger window (see below) |
-B, --break-on-start |
Pause before onLoad so breakpoints can be set first (implies --debug) |
-r DIR, --require-path DIR |
Directory searched by require() (default: directory of the script) |
Hardware
Components are declared with -H flags or via a JSON config file. Both can be combined; the config is loaded first, then -H flags are appended.
Inline syntax
-H <type>[:<param>[:<param>...]]
Available components
| Type | Spec | Notes |
|---|---|---|
display |
display[:WxH[:scale]] |
Renders to a window. Default size is 160x120 at scale 2. scale multiplies the window resolution. |
display_file |
display_file[:WxH] |
Writes {width, height, pixels} (packed RGB ints) to a pixels.json in the data directory instead of opening a window. Useful for headless testing, or custom display runtime. |
terminal |
terminal |
Text I/O window. send() writes to it; typed input is queued for getInput(). |
keyboard |
keyboard |
Keystroke capture window. |
motor |
motor |
Simulates a bearing or piston with adjustable speed, angle, and length. |
gps |
gps |
Provides position and velocity data, editable in the UI. |
radar |
radar |
Simulates radar returns. |
laser |
laser |
Distance measurement and hit detection. |
speaker |
speaker |
Audio stub (not yet implemented). |
light |
light |
Light control. |
hdd |
hdd[:label] |
Persistent storage backed by a JSON file. The label distinguishes multiple drives. |
network |
network[:channel] |
Channel-based packet messaging between network ports. |
antenna |
antenna |
Long-range relay between network ports. Named via the JSON config. |
hologram |
hologram |
3D holographic display. |
inputreg |
inputreg[:name[:value]] |
Named numeric input register, editable in the Register Manager window. |
outputreg |
outputreg[:name] |
Named numeric output register, shown as a bar. |
JSON config format
{
"hardware": [
{ "type": "display", "width": 320, "height": 240, "scale": 2 },
{ "type": "terminal" },
{ "type": "keyboard" },
{ "type": "hdd", "label": "main" },
{ "type": "antenna", "name": "tower" },
{ "type": "network", "channel": "eth0", "antenna": "tower" },
{ "type": "inputreg", "name": "throttle", "value": 0.5 }
]
}
emusc script.lua -c hardware.json
A few components take extra keys that have no inline equivalent: gps accepts starting x/y/z and vx/vy/vz, motor takes max_length, terminal takes max_lines, laser takes distance, max_range and has_hit, and network takes antenna to link the port to a named antenna.
Debugger
Run with -D / --debug to open the debugger window alongside the simulation. It has four tabs:
- Console - mirrors all Lua
print()output and runtime errors (regardless of whether--devis set), and takes commands. Anything that isn't a command gets evaluated as a Lua expression. Up/Down arrows cycle through history. - Stack - the Lua call stack, refreshed on breakpoint hits and errors (or manually).
- Variables - lists every global variable defined by your script (updated each tick), with its type and current value. Tables show a key count; functions show the source location. While paused at a breakpoint, the locals captured at that line appear here too.
- Breakpoints - enter a line number and click Add (or use
bpin the console). When execution reaches that line the simulation pauses mid-tick with a snapshot of the locals. Click Resume to continue or Step to advance one tick and pause again.
The console commands, in short:
g / p / step resume, pause, run one tick
bp N bc N bl set / clear / list breakpoints
k print the Lua call stack
? <expr> evaluate a Lua expression
dv [name] dump all variables, or inspect one
tick print the tick counter
Expression eval is disabled while paused at a breakpoint (the Lua VM is suspended inside the debug hook at that point), use dv or the Variables tab instead.
The Pause and Resume buttons in the toolbar also work outside of breakpoints - Pause halts the tick loop at the next tick boundary, Resume restarts it.
Starting with -B / --break-on-start pauses before onLoad runs, so you can place breakpoints before any of your code executes.
Modules and require()
require() in EmuSC takes a direct file reference, extension included:
-- if your script is at ~/projects/myapp/main.lua,
-- this loads ~/projects/myapp/utils.lua
local utils = require("utils.lua")
The require() function is identical to ScrapComputer's version of it. However, it resolves against a certain path, root path by default, instead of computer filesystem which, well, doesn't exist.
To resolve against a different root instead, pass --require-path:
emusc main.lua -H terminal --require-path ./lib
Writing scripts
Scripts use the same structure as in-game ScrapComputers programs:
Scripts are checked against Lua 5.1 syntax before they run: goto and labels, floor division, the 5.3 bitwise operators, <const>/<close> attributes and \x/\z/\u{} string escapes are all rejected with a line number, since the mod's VM would choke on them in-game. A bit library is available for bitwise work.
The emulator sets a global _EmuSC = true that never exists in-game. Use it to add emulator-only setup or to skip code that requires real hardware:
function onLoad()
if _EmuSC then
print("running in emulator")
end
end
For everything else, component getters, drawing calls, packet formats and so on, the API behaves as the ScrapComputers docs describe.
What it doesn't do
There is no game world behind the hardware, so anything that would measure the world is fed by hand instead:
- GPS, laser and radar values are whatever you enter in their windows. The GPS integrates velocity into position each tick; its rotation and acceleration fields stay zeroed.
- Holograms are tracked (create, move, delete all work) but nothing renders them.
- The speaker is silent -
playNote()and similar just flash what would have played in its window. - Cameras, seat controllers, gravity controllers, power sources and radar warning receivers aren't simulated. Their getters return empty tables, so loops over them are safe.
sc.powerreports fixed values;sc.midi,sc.nbsandsc.qrcodereturn nil; the audio, language, configuration and example managers are stubs.sc.lz4round-trips its own output but is not wire-compatible with real LZ4, andsm.noiseis a cheap approximation rather than true Perlin noise.
There is also no timeout or watchdog: if onUpdate contains a loop that never exits, the next tick never fires and the emulator freezes in place, matching in-game behavior where the computer simply stops responding. A stuck script stays stuck until you close the window.
Example
local display, terminal, keyboard
function onLoad()
display = sc.getDisplays()[1]
terminal = sc.getTerminals()[1]
keyboard = sc.getKeyboards()[1]
terminal.send("Ready.\n")
end
function onUpdate(dt)
local key = keyboard.getLatestKeystroke()
if key and key ~= "" then
terminal.send("Key: " .. key .. "\n")
end
display.clear(sm.color.new("001122"))
display.drawText(10, 10, "Hello", sm.color.new("ffffff"), nil)
display.update()
end
emusc myscript.lua -H display:320x240:2 -H terminal -H keyboard --dev
Persistent data
Each component gets a folder under <data-dir>/EmuSC/Hardware/, named after its type and index. HDDs keep a data.json in theirs; display_file components write their pixels.json to the same tree. The data directory defaults to the current working directory; use --data-dir to change it:
emusc script.lua -H hdd:main -d ./saves
This writes to ./saves/EmuSC/Hardware/hdd_0/data.json.
License
License text copyright © 2024 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
Terms
The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use.
Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate.
If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work.
All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor.
You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this License’s text to license your works, and to refer to it using the trademark “Business Source License”, as long as you comply with the Covenants of Licensor below.
Covenants of Licensor In consideration of the right to use this License’s text and the “Business Source License” name and trademark, Licensor covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor:
To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL Version 2.0 or a later version, where “compatible” means that software provided under the Change License can be included in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change Licenses without limitation. To either: (a) specify an additional grant of rights to use that does not impose any additional restriction on the right granted in this License, as the Additional Use Grant; or (b) insert the text “None” to specify a Change Date. Not to modify this License in any other way.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file emusc-1.0.2.tar.gz.
File metadata
- Download URL: emusc-1.0.2.tar.gz
- Upload date:
- Size: 44.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e72454b15a5422c4ea23215c4d624f4cc9e994971ea43eb5ea4c7cfb0542e034
|
|
| MD5 |
ea958935d0964a75fc5b94f6df4cbdca
|
|
| BLAKE2b-256 |
4ebf0fd090a50d4fea8fa525d59910cd1837770bf5c81024bab5eded23ccb29d
|
File details
Details for the file emusc-1.0.2-py3-none-any.whl.
File metadata
- Download URL: emusc-1.0.2-py3-none-any.whl
- Upload date:
- Size: 56.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02091411506bfd9f8172bcb26bc7b322b72b4801d5be773a115d2b669c5b3623
|
|
| MD5 |
a32c6b5abed920b8bac48b9e37502f4b
|
|
| BLAKE2b-256 |
1e05c3aa2b92fd7f629b738d12d0c505003faa2490c9dd76ae6c0380709aa036
|