This guide explains the new systems and improvements introduced in the
Abyssal Run template compared to the basic horror template
(Derelict Outpost A-13). It is meant to help you build
more complex projects with the same JSON-based engine.
You do not need to write JavaScript: you only edit the
GAME_DATA object in game_data.js.
Derelict Outpost A-13 – single viewport image/tileset, a few flags and events. :contentReference[oaicite:0]{index=0}Abyssal Run – multi-camera viewport with map, battery/pressure system, multiple endings and status commands. :contentReference[oaicite:1]{index=1}Both use the same general structure:
{
"title": "...",
"startRoom": "...",
"viewport": { ... },
"flags": { ... },
"rooms": { ... },
"events": [ ... ]
}
This guide focuses on what Abyssal Run adds or changes, not on the basics (which are covered in the previous general user guide). :contentReference[oaicite:2]{index=2}
In the basic template, the viewport uses a single image or tileset:
"viewport": {
"image": "assets/viewport_idle.png",
"frameWidth": 160,
"frameHeight": 90,
"fps": 6,
"mode": "tileset"
}
image: a single URL (path) to the PNG file.mode: "tileset": the image is animated as a horizontal tileset.type: "setViewport" with a new image URL. :contentReference[oaicite:3]{index=3}
In Abyssal Run, the viewport is a list of images with a numeric index:
"viewport": {
"images": [
"assets/map.png",
"assets/cam_shallow.png",
"assets/cam_rockpass.png",
"assets/cam_trench.png",
"assets/cam_kelp.png",
"assets/cam_ruins.png",
"assets/cam_trenchside.png",
"assets/cam_signal.png"
],
"current": 0,
"frameWidth": 350,
"frameHeight": 350,
"fps": 0,
"mode": "still"
}
images: an array of image URLs (strings) – this is your camera list + map. :contentReference[oaicite:4]{index=4}current: the index of the image currently displayed.mode: "still": images are static (no tileset animation).fps: 0: not used because images are not animated.In this template, the images are used like this:
images[0] → assets/map.png (global map)images[1] → shallow sector cameraimages[2] → rock passage camera
Instead of changing the viewport by passing a full URL every time, the template
uses indexes into the images array:
"cam": {
"text": "External camera activated.",
"actions": [
{ "type": "flag", "set": { "cam_active": true } },
{ "type": "flag", "add": { "battery": -10 } },
{ "type": "setViewport", "index": 1 }
]
}
Here:
index: 1 means “show viewport.images[1]”.index to select its own camera image.assets/ folder (for example assets/cam_lab.png).viewport.images, replace or add URLs:
"images": [
"assets/my_map.png",
"assets/cam_lab.png",
"assets/cam_hangar.png"
]
setViewport with the correct index:
{ "type": "setViewport", "index": 2 }
This system makes it easy to have many cameras and maps without repeating long image URLs everywhere.
Compared to the simple power_online / creature_alert flags in the
basic template, Abyssal Run introduces a more complex “submarine status”
system in flags: :contentReference[oaicite:5]{index=5}
"flags": {
"pressure_alert": 0,
"battery": 100,
"has_blackbox": false,
"cam_active": false
}
pressure_alert – how stressed the hull is (danger level).battery – remaining battery percentage.has_blackbox – whether the black box has been retrieved.cam_active – whether the external camera is currently on.
Local room actions add or remove values using type: "flag".
For example, many commands consume battery:
"sonar ping": {
"text": "Echo returns clean and stable.",
"actions": [
{ "type": "flag", "add": { "battery": -5 } }
]
}
In deeper or unstable sectors, some actions also increase pressure:
"sonar ping": {
"text": "Delayed echo from deep void.",
"actions": [
{ "type": "flag", "add": { "pressure_alert": 1 } },
{ "type": "flag", "add": { "battery": -5 } }
]
}
The “camera” action sets cam_active to true and consumes more battery:
"cam": {
"text": "Camera engaged.",
"actions": [
{ "type": "flag", "add": { "battery": -10 } },
{ "type": "flag", "set": { "cam_active": true } },
{ "type": "setViewport", "index": 2 }
]
}
You can use this pattern in your games: treat numeric flags as resources or meters (oxygen, sanity, temperature, etc.).
In the basic horror template, most events are local to rooms or simple
onCommand/timer events. Abyssal Run introduces a set of
global commands that always work, no matter which room you are in: :contentReference[oaicite:6]{index=6}
briefing – mission overview.status – internal status (battery, pressure, blackbox hint).environment – description of external conditions.commands – list of available commands.map – ASCII navigation map.cam – warning if the camera is already active.surface – attempt to surface (ending check).{
"trigger": "onCommand",
"command": "status",
"actions": [
{ "type": "log", "text": "STATUS:" },
{ "type": "log", "text": "Battery: drains with sonar, camera and adjustments." },
{ "type": "log", "text": "Pressure: increases in deep or unstable sectors." },
{ "type": "log", "text": "Blackbox: retrieve at signal source sector." }
]
}
This event does not directly read the numeric values, but it explains to the player how the system behaves. You could also add more advanced logs if you want.
{
"trigger": "onCommand",
"command": "map",
"actions": [
{ "type": "log", "text": "NAVIGATION MAP:" },
{ "type": "log", "text": " [06]" },
{ "type": "log", "text": " │" },
{ "type": "log", "text": " [07]" },
...
]
}
This shows a simple ASCII map in the terminal, helping the player understand the structure of rooms/sectors.
There is also a global event for cam that triggers only if the
camera is already active:
{
"trigger": "onCommand",
"command": "cam",
"conditions": [ "flags.cam_active === true" ],
"actions": [
{ "type": "warn", "text": "Camera already active." }
]
}
This is a good pattern: use onCommand + conditions to create
global feedback for your “systems” flags.
The basic horror template already showed endings via:
{ "type": "end", "text": "..." } in actions and events. :contentReference[oaicite:7]{index=7}
Abyssal Run expands this with a mission objective (blackbox)
and multiple possible endings:
In the sector_signal_07 room, the player can retrieve the blackbox:
"retrieve object": {
"conditions": [ "flags.has_blackbox === false" ],
"text": "Blackbox secured.",
"actions": [
{ "type": "flag", "set": { "has_blackbox": true } },
{ "type": "flag", "add": { "battery": -5 } }
]
}
This sets has_blackbox to true and slightly reduces battery.
The success ending is triggered with a global surface command,
but only if:
{
"trigger": "onCommand",
"command": "surface",
"conditions": [
"flags.has_blackbox === true",
"state.currentRoomId === 'sector_shallow_01'"
],
"actions": [
{ "type": "log", "text": "Surface ascent initiated." },
{ "type": "end", "text": "ENDING: RECOVERY COMPLETE" }
]
}
This pattern is very useful: you can require both a flag and a specific room to unlock the ending.
Two timers watch the pressure_alert and battery flags:
pressure_alert >= 5 for long enough → hull implosion ending.battery <= 0 for long enough → power failure ending.{
"trigger": "timer",
"delay": 24,
"repeat": true,
"conditions": [ "flags.pressure_alert >= 5" ],
"actions": [
{ "type": "err", "text": "Critical hull stress detected." },
{ "type": "end", "text": "ENDING: HULL IMPLOSION" }
]
}
{
"trigger": "timer",
"delay": 30,
"repeat": true,
"conditions": [ "flags.battery <= 0" ],
"actions": [
{ "type": "err", "text": "Battery depleted. Systems shutting down." },
{ "type": "end", "text": "ENDING: POWER FAILURE" }
]
}
You can reuse this technique for any kind of “slow failure” system: sanity, infection, fuel, etc.
There is also a one-shot timer that checks if pressure_alert
remains 0 for a long time:
{
"trigger": "timer",
"delay": 180,
"repeat": false,
"conditions": [ "flags.pressure_alert === 0" ],
"actions": [
{ "type": "log", "text": "Silent drift detected. Water conditions unusually calm." },
{ "type": "end", "text": "SECRET ENDING: NO DISTURBANCE DETECTED" }
]
}
This shows how you can reward “careful” play with a hidden ending.
Here is a simple workflow to use Abyssal Run as a base for your own
complex game:
title, change room names and descriptions, but keep the
structure (flags, events) as a reference.
oxygen, sanity, temperature, infection.
Initialize them in the flags object.
type: "flag" with add and set.
status event and adjust the text to describe
your systems to the player.
index 0 for a global map
if you like. Use setViewport with index in room actions.
repeat: false and a special condition (for example,
“never triggered alarm”, or “never used weapon”).
briefing, status, environment,
commands, map, cam, surface) to give a “control panel” feeling.
You can mix and match these patterns to design your own advanced games while
still working only inside the GAME_DATA JSON file.