Room Classification data/rooms/rooms.json

How the game decides what kind of room a given enclosed area is. Audience: modders editing data/rooms/rooms.json or the trigger definitions in data/condtrigs/condtrigs.json.

How it works

1 · GEOMETRY Tiles flood-fill across non-wall, non-portal neighbors. Walls block; doors and airlock doors (IsPortal) are the boundary markers. Result: a Room with a tile list and a synthetic Compartment CondOwner.
2 · SORT Every entry in rooms.json is sorted by nPriority descending, so high-priority specs are tested first.
3 · MATCH First spec whose Matches() returns true wins. Checks: pressurization gate, tile-count bounds, all aReqs satisfied, no aForbids hit.
4 · LIVE Re-runs whenever an installed CondOwner enters or leaves the tile set. Mounting a Nav Station retags the room immediately — no save/load needed.

What "Value ×" actually does

Sale-price multiplierThe room's fValueModifier multiplies the base price of every installed CondOwner inside it when the ship's sale value is computed. It is not a stat boost, an atmosphere buff, or a passive resource — it's strictly a price multiplier on the contents.

Room.CalculateRoomValue(): RoomValue = Σ ( co.GetBasePrice() × fValueModifier ) for each installed co in room

So when you mod a new room with fValueModifier: 1.5, you're saying "anything installed in this layout is worth 50% more when the ship sells." Source: Room.cs:206 in the decompiled assembly.

The full ship-value formula

Ship.GetShipValue(): shipValue = ( Σ room.RoomValue across all rooms ) × o2PumpBonus o2PumpBonus = 3.0 if ship has any installed O2 pump, else 1.0

That's the entire calculation (Ship.cs:8632). Two contributors: per-room contents multipliers (already inside RoomValue) and a flat ×3 bump for having any O2 pump. No variety bonus — the game does not count distinct room types or reward diversity directly. The "variety effect" is implicit: spreading installed items across specialized rooms means each item picks up a higher multiplier than it would in a single Blank box.

GetPartsValue() is a separate "strip for parts" number — Σ StatBasePrice × damage_state across all installed COs, no room multiplier. Used for piecemeal sale, not whole-ship sale. Derelicts use GetShipValue() wrapped by a break-in factor and a deterministic-random per-ship modifier.

Walls and wall-mounted equipment

Asymmetric handlingWalls and wall-mounted equipment are routed to rooms differently — and a wall-mounted item's use point, not its actual position, decides which room credits its value.

Tile.AddToRoom(tile, co, addEffects): if (tile.room == null) // wall tile, no flood-fill room pos = co.GetPos("use", false) // ← fall back to use-point tile = ship.GetTileAtWorldCoords1(pos) if (tile == null || tile.room == null) return // still nothing → CO gets $0 tile.room.AddToRoom(co, addEffects)

So the real maximization play isn't just "stuff a valuable item into LuxuryQuarters" — it's: lay the high-multiplier room out so its enclosed area swallows the interior interaction tiles of the highest-base-price wall-mounted equipment (engines, rotors, weapons, RCS thrusters), while still satisfying that room's reqs and forbids and not tripping a higher-priority spec.

Decision order

First match wins. Click a row to jump to its detail card; Esc to clear focus.

Pri Spec Friendly Min tiles Value × Pressurized?

Per-room reference

Trigger glossary

The aReqs / aForbids strings are CondTrigger references with =chance×count syntax, e.g. TIsChairInstalled=1.0x4 = "≥4 installed chairs". A few are meta-triggers defined in condtrigs.json with bAND: false that OR over a sub-list:

Meta-triggerSatisfied by any of
TIsRoomEngineering TIsCanister· TIsChargerBattery04Installed· TIsShipBatteryInstalled· TIsRCSDistroInstalled
TIsRoomWellnessOptionals01 TIsFridge01Installed· TIsSinkInstalled· TIsTreadmillInstalled· TIsStrengthTrainerInstalled
TIsRoomRecreationOptionals TIsTerminalInstalled· TIsTVInstalled· TIsBartopInstalled
TIsRoomCargo TIsStorageBinInstalled· TIsRackInstalled
TIsRoomCargoExterior TIsCargoWeb01Installed· TIsStorageBinInstalled
TIsCanister TIsRTAInstalled· TIsCanister01Installed· TIsCanisterLH02Installed· TIsCanisterLHe02Installed
TIsShipBatteryInstalled TIsBattery02Installed· TIsBattery02bInstalled· TIsBattery02cInstalled

Plain (non-meta) triggers used in rooms.json bind directly to a CondOwner test:

TriggerRequires (aReqs)Forbids
TIsReactorICIsReactorIC
TIsNavStationInstalledIsNavStation, IsInstalledIsDamaged
TIsTowingBraceInstalledIsEquipmentTowing, IsInstalledIsDamaged
TIsDockSysInstalledIsDockSys, IsInstalled
TIsBedInstalledIsCushion, IsSheet, IsInstalledIsDamaged, IsSlotted, IsInContainer, IsCarried
TIsToiletIsContainer, IsOpening10cmUp, IsWaterproof, IsInstalled
TIsSinkInstalledIsSink, IsInstalledIsDamaged
TIsTableInstalledIsTable, IsInstalled
TIsChairInstalledIsChair, IsInstalledIsDamaged
TIsFridge01InstalledIsFridge01, IsInstalled
TIsStorageBinInstalledIsStorageBin, IsInstalledIsDamaged
TIsLightSourceInstalledIsLightSource, IsInstalledIsDamaged
TIsHatchInstalledIsHatch, IsInstalledIsDamaged
TIsRCSDistroInstalledIsRCSReg, IsInstalled
TIsRTAInstalledIsRTA, IsInstalledIsDamaged

Pairs and oddballs

Most rooms are exactly what the JSON says. Five cases need extra context that a naive read of the JSON won't surface:

PairBridge (Closed) vs Bridge (Open)

Both require only TIsNavStationInstalled. The classifier between them is the forbid list: BridgeRoom (90, ×1.5) forbids RCS distro, recreation, wellness, toilet, reactor, and bed; BridgeArea (80, ×1.3) forbids nothing.

A "clean" bridge gets the closed bonus; a Nav Station co-located with anything else falls through to Open. This forbids-as-purity-tier pattern is unique to the Bridge pair — no other room types share aReqs and split on aForbids. Copy this pattern when modding a new specialized room with a graceful fallback.

OddballCargoRoomExterior — only bAllowVoid: true

The pressurization gate is binary: a spec wants either a void or a pressurized room, and an unpressurized enclosed area can only be CargoRoomExterior or Blank. If you mod a new void-permitting room, you must explicitly set bAllowVoid: true or the spec will never match.

GotchaReactor priority 100, no forbids

First-hit-wins plus no forbid list means an IC reactor turns any 4+-tile pressurized room into a Reactor regardless of what else is installed. Bed + reactor → Reactor (not Quarters). Nav Station + reactor → Reactor (not Bridge). For a coexisting "engineering bay with a reactor" classification you need a higher-priority spec that requires both the reactor and the engineering items.

HiddenMeta-trigger expansion — five rooms hide their real requirements

Reading the JSON literally, Engineering requires TIsRoomEngineering. That's not enough for a modder — the actual requirement is any one of four sub-triggers. Same for WellnessRoom, Recreation, CargoRoom, CargoRoomExterior. A site that auto-renders rooms.json without resolving meta-triggers will look correct but be useless to a modder asking "what do I install to get a Wellness Room?" The cross-reference resolver should follow aTriggers on bAND: false triggers and render the OR-set.

Edge caseBlank — fallback, not a match

IsBlank is short-circuited inside Matches(), so Blank's aReqs, aForbids, and nMinTileSize are dead fields. The row is just a holder for strNameFriendly / strIconName / fValueModifier. Blank is assigned via the fallback _roomSpec ??= dictRoomSpec["Blank"] at the end of CreateRoomSpecs(), never picked by the priority loop.

Modding notes

Generated from data/rooms/rooms.json, data/condtrigs/condtrigs.json, and the decompiled classifier in decomp/Assembly-CSharp/Room.cs / Ostranauts/Ships/Rooms/RoomSpec.cs.