Rain World keeps world membership in abstract objects and active simulation in realized objects. A rock can remain a global::AbstractPhysicalObject while its room is unloaded, then become a global::Rock when the room is prepared for play.

global::AbstractWorldEntity stores its global::World, global::WorldCoordinate, and global::EntityID. Its Room property resolves the abstract room from pos.room. global::World.GetAbstractRoom(System.Int32) returns null for an index outside the world’s room range, so a coordinate must belong to the intended world before realization begins.

flowchart LR
    A[AbstractPhysicalObject] -->|registered by AddEntity| B[AbstractRoom.entities]
    A -->|Realize or RealizeInRoom| C[PhysicalObject]
    C -->|back reference| A
    C -->|PlaceInRoom| D[Room.updateList]
    C -->|by collision layer| E[Room.physicalObjects]
    D -->|room unloads and Abstractize runs| A

Realization has two parts

global::AbstractPhysicalObject.Realize() creates the concrete object selected by the abstract type. For global::AbstractPhysicalObject.AbstractObjectType.Rock, it creates global::Rock. The global::PhysicalObject constructor assigns itself to abstractPhysicalObject.realizedObject.

Construction does not register that physical object with a realized room. global::AbstractPhysicalObject.RealizeInRoom() handles the wider placement sequence. It returns immediately for a den position. Otherwise it calls Realize(), resolves an undefined tile from the room node when needed, and places the realized object through global::PhysicalObject.PlaceInRoom(global::Room). Connected realized objects are placed through the same operation.

This distinction explains a common intermediate state: realizedObject can be non-null while realizedObject.room is still null. Code that needs collision layers, drawing, or room updates must check room membership rather than treating a non-null concrete object as fully placed.

Abstract and realized registration

The two layers have separate collections:

  • global::AbstractRoom.AddEntity(global::AbstractWorldEntity) records abstract membership. It also tracks abstract creatures in their dedicated collection and checks for duplicate abstract entries.
  • global::Room.AddObject(global::UpdatableAndDeletable) appends the object to updateList, assigns obj.room, registers physical objects in a collision layer, and arranges drawable registration.
  • global::PhysicalObject.PlaceInRoom(global::Room) calls Room.AddObject for the concrete object.

global::Room.AddObject(global::UpdatableAndDeletable) does not add the abstract entity. It also does not perform the same duplicate check for ordinary updateList insertion. Calling room.AddObject(item.realizedObject) after item.RealizeInRoom() can insert the realized object twice.

For a new ordinary physical item in a room that is already prepared, the source-verified pattern is:

construct the appropriate abstract subtype with a valid WorldCoordinate and new EntityID
room.abstractRoom.AddEntity(item)
item.RealizeInRoom()

global::Player.SpitUpCraftedObject() follows this order when it replaces an ordinary spear with an explosive abstract spear. Specialized abstract subclasses may require their own constructor data or realization override, so this sequence does not define every object type.

Room preparation

global::AbstractRoom.RealizeRoom(global::World, global::RainWorldGame) constructs a global::Room, queues a global::RoomPreparer, assigns realizedRoom, and adds it to world.activeRooms. The existence of realizedRoom comes before preparation finishes.

Later readiness stages populate the room. global::Room.ShortCutsReady() realizes eligible non-creature entities, excluding den entities and objects connected to creatures. global::Room.ReadyForAI() realizes allowed remaining creatures and updates readiness flags. A hook that runs during room loading needs a verified readiness condition for the data it accesses.

Rooms and regions follows the earlier path from room files through global::WorldLoader to an abstract room. This page starts once those structures exist.

Unloading and retained references

global::AbstractRoom.Abstractize() abstracts the room’s entities and clears realizedRoom after unloading. global::AbstractPhysicalObject.Abstractize(global::WorldCoordinate) removes its physical object from the room and clears realizedObject. Some paths may destroy the abstract object as well.

Store an abstract identity or re-find the object when work crosses a room transition. A retained global::Rock can be stale even if the corresponding abstract entity continues to exist. Abstract survival across room unloading also does not prove save file persistence. Save serialization was outside this verification.

The marker in Object interaction avoids this lifetime problem by scanning the player’s current global::Room.physicalObjects each fresh jump. It does not create an abstract object because its global::Spark is a short cosmetic effect added directly to the active room. Hook lifecycle covers when that scan runs, and Runtime reference identifies the assembly used to verify these methods.

Sources

  • Runtime reference identifies the installed game assembly and hash used for these symbols.