Skip to content

Levels with SceneTools

Mark Rogers edited this page Oct 18, 2026 · 1 revision

The game has no level editor (October 2026). Levels are Unity scenes made in the developer's SceneTools project and exported as .sobj bundles, which a mod folder carries like any other file. The madnesswright-scenes skill walks through that project, lints the saved scene with mpn.py scene-lint, and runs the build, deploy and playtest loop. Its rules come from the official SceneTools Overview and Event System Overview, read on 2026-10-17 (madnesswright-scenes RESEARCH.md).

Setup

  1. The game on releasebeta with a fresh backup (Getting started).
  2. The project from github.com/MPN-Modders/MPN_SceneTools. It has no license file, so its files must not be redistributed; madnesswright copies none of them. Pull it again before a session, since commits land every few weeks.
  3. Unity 2019.2.6f1 exactly, installed through Unity Hub from the download archive. You install it; the agent does not.
  4. Copy Assets/Scenes/SamplePlayground_PG.unity or SampleSiegeArena.unity rather than editing the samples.

What you can build

  • A Playground scene: its name ends in _PG, and it shows in the Playground scene list (the terminal by the control room door). The fastest test loop, so the skill builds here first even for a stage.
  • A custom stage in a campaign: needs a Stage card and a Campaign card, with Story rules (no Siege or Infiltration). A stage with no valid SceneName or no RequiredCharacters is dropped by the game.
  • An Arena Siege or Infiltration stage: as of August 2026 a custom arena stage can only replace an existing one, and the tools are marked "testing".

The card fields for stages, campaigns, cutscenes, events and missions are in the skill's references/stages-and-campaigns.md, and the objects, layers and rules in references/scenetools-rules.md.

The loop

  1. Block out the room: drag in Room_Layout, click SYNCH VISUALS, never rotate a room. Floors get Tag Floor, Layer Terrain and Static. Exactly one Player_Start, under the room's Characters object, and at least one waypoint per room.
  2. Plan encounters before placing enemies. The skill writes LEVEL-PLAN.md into the mod folder, one line per room, so the playtest has something to check against.
  3. Save the scene (Unity writes it as text) and run mpn.py scene-lint on it.
  4. Give the scene an Asset Label, then build from the menu Madness, AssetBundle, >> Build (Compressed) *RECOMMENDED*. The .sobj lands in %USERPROFILE%\AppData\LocalLow\Gibbing Tree\Madness Project Nexus SceneTools\SceneObjs\, and mpn.py where lists the newest under tool_outputs.scenetools.
  5. Copy the .sobj into the mod folder with any Stage, Campaign, Event or Cutscene cards, then check-mod and deploy.
  6. You playtest against the checklist the skill fills in (spawn, camera borders, each door, enemy pathing, each encounter clearable), then mpn.py log. One change at a time.

A headless build through Unity.exe -batchmode is described in the skill but unverified: no 2019.2.6f1 editor was available to test it.

What scene-lint checks

It reads the .unity file and its .meta, finds the SceneTools project by walking up to the folder holding Assets and ProjectSettings (or takes --project), and names prefab instances from the project's .prefab.meta files. Rule ids come from the official docs.

Id Rule Reported as
S01 exactly one Player_Start in the scene error
S02 Player_Start parented to the room's Characters error
S03 rooms never rotated error
S04 Zone_Camera not rotated error
S05 Zone_Camera under the room's Waypoints error
S06 at most one SceneRules error
S07 SceneRules in the scene root warning
S08 objects tagged Floor on Layer Terrain error
S09 objects tagged Floor marked Static error
S10 no camera other than Dev_Cam (delete the default Main Camera) warning
S11 no default Directional Light warning
S12 at most 4 active lights (3 on low quality) warning
S13 the scene has an Asset Label, or >> Build skips it error

It also warns when it finds no Room_Layout instance or no object tagged Floor, and reports in info.playground_listed whether the name ends in _PG. A missing _PG is a note, not a problem: stage-only scenes need not show in the Playground.

The limits, from the code: it sees the scene file only, not in-game behaviour, pathing or event logic. Floors inside room prefabs are not checked, unpacked rooms are not detected, and S12 counts every light in the scene file against four per room rather than the lights active at one time.

Examples

The repository's fixture project (tests/fixtures/scene-project, synthetic) has two scenes. Arena_PG.unity has no Player_Start and puts its Zone_Camera under Characters:

python mpn.py scene-lint scene-project/Assets/Scenes/Arena_PG.unity
{
  "scene": "scene-project\\Assets\\Scenes\\Arena_PG.unity",
  "project": "scene-project",
  "errors": [
    "S01 every scene needs exactly one Player_Start; found 0",
    "S05 Zone_Camera must be parented to its room's Waypoints object or it is not detected; parent is Characters"
  ],
  "warnings": [],
  "info": {
    "prefab_counts": {
      "Room_Layout": 1,
      "SceneRules": 1,
      "Zone_Camera": 1
    },
    "rooms": 1,
    "floors_tagged": 1,
    "lights_in_scene_file": 0,
    "asset_label": "arena_pg",
    "playground_listed": true
  }
}

Exit code 0.

Warehouse_PG.unity has a rotated room and an empty Asset Label:

python mpn.py scene-lint scene-project/Assets/Scenes/Warehouse_PG.unity
{
  "scene": "scene-project\\Assets\\Scenes\\Warehouse_PG.unity",
  "project": "scene-project",
  "errors": [
    "S03 room Room_Layout is rotated; rooms must never be rotated",
    "S13 the scene has no Asset Label (AssetBundle name); >> Build will not export it"
  ],
  "warnings": [],
  "info": {
    "prefab_counts": {
      "Player_Start": 1,
      "Room_Layout": 1,
      "SceneRules": 1,
      "Zone_Camera": 1
    },
    "rooms": 1,
    "floors_tagged": 1,
    "lights_in_scene_file": 0,
    "asset_label": null,
    "playground_listed": true
  }
}

Exit code 0.

Like every command, scene-lint exits 0 even with errors. Put --strict before the command name to exit 1 when there are errors:

python mpn.py --strict scene-lint scene-project/Assets/Scenes/Warehouse_PG.unity
{
  "scene": "scene-project\\Assets\\Scenes\\Warehouse_PG.unity",
  "project": "scene-project",
  "errors": [
    "S03 room Room_Layout is rotated; rooms must never be rotated",
    "S13 the scene has no Asset Label (AssetBundle name); >> Build will not export it"
  ],
  "warnings": [],
  "info": {
    "prefab_counts": {
      "Player_Start": 1,
      "Room_Layout": 1,
      "SceneRules": 1,
      "Zone_Camera": 1
    },
    "rooms": 1,
    "floors_tagged": 1,
    "lights_in_scene_file": 0,
    "asset_label": null,
    "playground_listed": true
  }
}

Exit code 1.

A scene saved in binary is refused with the setting to change:

python mpn.py scene-lint Binary.unity
{
  "scene": "Binary.unity",
  "errors": [
    "scene is binary; set Edit > Project Settings > Editor > Asset Serialization to Force Text"
  ],
  "warnings": [],
  "info": {}
}

Exit code 0.

On Linux and macOS the scene path prints with forward slashes.

Clone this wiki locally