ClientMod (2023-03-21): opt-in Rec Royale night mode. RRO scenes never
enable the room mood engine (no data blob → SceneMoodSettings.isEnabled
stays false, CurrentMood null, the per-frame update returns at once), so
the mod replays the room-data loader's bring-up, registers a shipped
night MoodSetting (Night_Calm_Outdoor_Mood by default) as the scene's
default mood, supplies a skybox material on the Custom/SkyBox shader
(MoodConfig.SkyBoxMaterial is not loaded outside custom rooms), assigns
the scene's directional light as RenderSettings.sun, matches the key
light to the engine-driven sun, darkens via the mood's post-exposure and
sun/ambient intensities, forces post-processing on, keeps or disables
fog, hides the daytime cloud meshes, unlocks weapon flashlights, and
restores everything on hotkey-off / scene change. All knobs live in
dorknet-clientmod.json (RecRoyaleNight*), written by install-melon.ps1.
Server/admin: baked Rec Room Originals are seeded with an empty
DataBlobName, which the 2023 client reads as "no room data" — no Maker
Pen, no CV2 room-settings chips, no saves, and for Rec Royale no game at
all (its game-rule chip RecRoyaleSolos_GameRuleWrapperCircuit ships in
the blob, not the scene). New admin actions on the Sub-rooms tab:
POST rooms/{id}/subrooms/{sub}/blob/enable stamps the synthetic default
blob name (which CdnController already answers with the RRO-editable
blob) and blob/reset clears it back to scene-only. IsRRO is derived from
IsAGRoom, so the client keeps loading the baked map.
Docs: README section, docs/admin.md procedure, docs/recroom-2023-room-save.md,
new docs/recroom-2023-scene-moods.md with the ISIL offsets behind all of it.
Tests: RroRoomBlobTests.
12 KiB
Rec Room 2023-03-21 — scene moods (sky dome / sun / fog) and Rec Royale night mode
Client-side reference for the room "mood" engine the March-2023 client uses
for Maker Pen sky/sun/fog settings, and how DorkNet.ClientMod
(RecRoyaleNight.cs) reuses it to give Rec Royale a night variant the game
never shipped. Everything below was read from the Cpp2IL ISIL dump
(C:\tmp\recroom-2023-03-21-isil), the interop stubs
(C:\tmp\recroom-2023-03-21-decompiled) and the built game data
(RecRoom_Data\resources.assets, globalgamemanagers).
Ownership chain
| Object | Where | Notes |
|---|---|---|
RecRoomSceneManager |
MonoBehaviourPun, one per room scene |
static get_Instance (interop property name obfuscated: PDCOCJBICGC); name-preserved field sceneMoodSettings |
RecRoom.Core.Scene.SceneMoodSettings |
plain class held by the scene manager | initialised by PCJGOACJEDL(RecRoomSceneManager) at scene start |
RecRoom.Core.Creation.SceneMoods.MoodSetting |
ScriptableObject asset |
FriendlyName, SkyBoxSettings, SunSettings, FogSettings, BloomSettings, ColorGradingSettings, BackgroundObjectsSettings, VolumeProfile, BakedReflection, guid |
SceneMoodSettings name-preserved members: availableMoods
(MoodSetting[]), defaultMood, CurrentMood, PrevMood,
CurrentRoomMoodIndex, SupportsMoodCircuits, HasRecRoomSkyboxShader,
HasSunSource, Volume, DefaultVolumeProfile, ReflectionIntensity,
AmbientLightIntensity, plus one PFFMDAJPHJO<T> blender per settings
block (SkyBoxMoodSettingsBlender, SunMoodSettingsBlender, …).
Apply path (what actually changes the picture)
SceneMoodSettings.MLLBALMKEGJ(MoodSetting) — ISIL
RecRoom/Core/Scene/SceneMoodSettings.txt:3025-3581:
ALCMNGKOEBK()— pushes the mood's six settings blocks into the blenders as the "top" data (6× generic blender set call).OEKNGEMDMDB(VolumeProfile)— swaps the URP post-processing profile.RenderSettings.set_skybox— the shared Rec Room skybox material; theSkyBoxMoodSettingsblender then writes_SkyTopColor-style shader props into it (SkyBoxMoodSettings.FPKCBLPBOFN(Material)).RenderSettings.get_sun→ re-aims/recolours the sun (SunMoodSettings, only whenHasSunSource).ReflectionProbe.set_customBakedTexture←MoodSetting.BakedReflection.- 6× blender force-update.
It does not write CurrentRoomMoodIndex, so calling it is local only.
RRO scenes: the engine starts disabled
SceneMoodSettings.isEnabled (+0x38) gates everything: get_CurrentMood
returns null while it is false, get_RoomMoodsEnabled is
isEnabled && defaultMood != null, and nothing ticks. The flag is only
switched on by the room data-blob loader (EIMFCPIHEPF.JPKEMACDNCF, ISIL
EIMFCPIHEPF.txt:2136-2310), which custom rooms run and RRO scenes (no
data blob) never do. Its bring-up sequence:
| Call | Meaning |
|---|---|
LKIKMLNPCFK(false) |
SetEnabled(false): applies a null mood (blenders reset, default VolumeProfile back) and toggles BackgroundObjectsRoot |
LIEDFOLBGHL() |
Capture: builds the blenders' "previous" data from RenderSettings.skybox / sun / fog; sets hasRecRoomSkyboxShader = skybox shader name == "Custom/SkyBox" |
LKIKMLNPCFK(true) |
SetEnabled(true) |
The apply step then swaps RenderSettings.skybox to the global
MoodConfig skybox material (LNMDIIFOFPD.NHAEHKJINFH, +0x18).
The ClientMod replays this exact sequence when it finds isEnabled false.
The apply step only sets blend targets. The per-frame update
CEKLEBHDAHA (ISIL :3895-4031) is what actually writes sky colours,
rotates/recolours the sun, blends fog and background objects and (via
CPJBHLMCBBC) pushes bloom/colour grading into the Volume — and it
returns immediately while get_CurrentMood() is null. get_CurrentMood
(ISIL :297-457) returns null when isEnabled is false, else
_studioRoomUnityMood if set, else availableMoods[index] when the
synced index is set, else defaultMood. RRO scenes have defaultMood
null and availableMoods empty, so even a fully enabled engine with a
successful apply shows only the ungated background objects (moon, stars).
The ClientMod therefore writes the night mood into defaultMood and a
one-element availableMoods before enabling the engine.
Two flags captured by LIEDFOLBGHL gate what the apply step and the
per-frame update are allowed to touch:
| Flag | Set by capture from | Gates |
|---|---|---|
hasRecRoomSkyboxShader (+0xA8) |
RenderSettings.skybox.shader.name == "Custom/SkyBox" |
sky colour blender apply |
hasSunSource (+0xA9) |
RenderSettings.sun != null |
sun direction / colour / intensity |
Note the sun flag is nested inside the sky check: hasSunSource can only
become true when the skybox shader test already passed (ISIL :2629-2705).
RecRoyale_Frontier fails both at capture time: its skybox is not the
mood shader. The observable result of a naive apply is exactly "moon and
stars appear, sky and sun unchanged" — the mood's
BackgroundObjectsSettings are not gated.
Worse, the apply step's own skybox swap does not help in RRO scenes: it
assigns MoodConfig.SkyBoxMaterial, and that material
(Skybox_Moods_Sunny_Default_Mat, sharedassets77.assets — a custom-room
scene's shared assets) is not loaded there, so RenderSettings.skybox
becomes null (plain black sky) and both flags stay false forever. The
Custom/SkyBox shader itself is in resources.assets, so
new Material(Shader.Find("Custom/SkyBox")) works anywhere.
Workarounds used by the ClientMod: assign the scene's directional Light
to RenderSettings.sun if unset; apply once; then put a material on the
Custom/SkyBox shader into RenderSettings.skybox and
MoodConfig.SkyBoxMaterial; capture again so both flags flip true; then
apply for real. Post-exposure additionally needs the scene Volume
enabled with weight 1 and UniversalAdditionalCameraData.renderPostProcessing
on (RecRoyaleNightForcePostProcessing). LILOMACBFPI() (:2789-2817) force-sets
both flags to true (writes 0x0101 at +0xA8) and clears
_studioRoomUnityMood; it is the Studio path and not used here because
a true hasSunSource with a null sun would NRE in the update loop.
Fog: FogMoodSettings.NLDDIDEIEFH() reads RenderSettings.fog,
fogStartDistance, fogEndDistance, fogColor into the block;
KGBEBONMIII() writes them back. Swapping MoodSetting.FogSettings for
a freshly captured block before the first apply keeps the scene's fog
(RecRoyaleNightFog: false). The block exposes only color,
startDistance, fadeDistance (no enabled flag), so "fog off"
(RecRoyaleNightDisableFog) pushes both distances to 1e6 and forces
RenderSettings.fog = false after each apply.
Frontier's daytime clouds are scene meshes, not mood background objects:
CloudPlane ×19 under Clouds, static-batched as
StaticObjectsStaticBatch_Cloud_RecRoyale_Frontier_Mat (level106).
RecRoyaleNightHideObjects disables renderers whose object or
sharedMaterial name contains a pattern (default Cloud).
Brightness: the mood blocks are plain data the ClientMod edits in place before the apply (originals restored on scene exit / toggle-off):
| Block · field | Applied as | ClientMod knob |
|---|---|---|
ColorGradingMoodSettings.exposure |
ColorAdjustments.postExposure via VolumeParameter<float>.Override in KGBEBONMIII |
RecRoyaleNightExposure (EV offset) — the only knob that dims baked lightmaps |
SunMoodSettings.sunIntensity |
RenderSettings.sun.intensity (needs hasSunSource) |
RecRoyaleNightSunScale (×) |
| — | other directional Lights (never touched by the engine; Frontier's RenderSettings.sun is SkyboxLight, the sun-disc light, not the key light) |
RecRoyaleNightMatchSunLights: copy the driven sun's rotation/colour, intensity × RecRoyaleNightSunScale |
SkyBoxMoodSettings.ambientLightIntensity |
RenderSettings.ambientIntensity |
RecRoyaleNightAmbientScale (×) |
Other ColorGradingMoodSettings fields: color, temperature, tint,
hueShift, saturation, contrast, colorFilterIntensity.
BloomMoodSettings: color, diffusion, intensity, threshold.
Networked path (do not use for a client-side effect)
SceneMoodSettings.IPAMPMBDPNB(Guid) scans availableMoods for the GUID
and calls set_CurrentRoomMoodIndex. _currentRoomMoodIndex is a
DFAOKLDJPEL<int> from RecRoom.Networking.SynchronizedFields.Runtime,
i.e. a synchronized field that replicates to everyone in the instance.
CircuitsV2 room-settings chips (RecRoom.CircuitsV2.RoomSettings.RoomSkydome,
RoomSun, RoomFog, all RoomMoodBase<T,U,V>) drive the same engine
through ModifyMood(...) and are also networked.
Shipped mood assets
All live in resources.assets and are registered in the globalgamemanagers
ResourceManager table under their lower-cased asset name (no folder), so
Resources.Load("<name>", typeof(MoodSetting)) works.
| Asset | FriendlyName |
|---|---|
Night_Calm_Outdoor_Mood |
Calm Night |
Night_Spooky_Outdoor_Mood |
Spooky Night |
Night_Wild_Outdoor_Mood |
Wild Night |
Night_StuntRunner_Outdoor_Mood |
(Stunt Runner night) |
OuterSpace_Mood |
Outer Space |
Sunset_Cozy_Outdoor_Mood |
Cozy Sunset |
Sunset_Burning_Mood |
Burning Sunset |
Sunset_Tropical_Outdoor_Mood |
Tropical Sunset |
Sunrise_Misty_Outdoor_Mood |
(misty sunrise) |
Reflection cubemaps ship alongside (Moods_WildNight_ReflectionProbe,
Moods_SunnyDay_Skybox_Reflection_Baked, …) and are referenced from each
MoodSetting.BakedReflection.
Rec Royale specifics
- Only one Rec Royale map in this build:
Assets/Activities/RecRoyale/Maps/RecRoyale_Frontier/RecRoyale_Frontier.unity(Unity scene nameRecRoyale_Frontier). The"recroyale"literal inBattleRoyaleManageris onlyget_RuleTypeAutoTag, not a scene name. BattleRoyaleManagerhas no static instance accessor; scene-name matching via MelonLoaderOnSceneWasLoadedis the detection choke point.- Weapon flashlights already exist:
WeaponFlashlightController(fieldsweapon,flashlightPrefab,flashlightRoot) spawnsWeaponFlashlightwhenget_FlashlightAllowedis true. That getter (ISILWeaponFlashlightController.txt:3-75) isMatchmaking.FBELDNLEKAC() != null && [obj+0x28] == 15— a game-mode gate for a different activity. Interop property name:KIMMMCCKMJL.
Real CV2 mood chips in an RRO scene
The Maker Pen chips ("Skydome Constant", "Room Skydome Modify/Reset", and
the Sun / Fog / Background Objects trios) drive RoomSkydome, RoomSun,
RoomFog, RoomBackgroundObjects (RecRoom.CircuitsV2.RoomSettings,
all RoomMoodBase<T,U,V>), which hang off RoomElementSettingsManager (a
NetworkedSingletonMonoBehaviour that CircuitsV2Manager brings up in
MPDIMHOMIDP). Their state persists in the room blob
(PersistedRoomData fields 32-35: room_fog_data, room_mood_sun_data,
room_mood_background_objects, room_mood_skydome). None of that runs in
a room the client believes has no data blob, so the prerequisite for chips
in Rec Royale is server-side: give the room a blob (docs/admin.md → "RRO
room data blob"). The ClientMod probe line
[royale-night] CV2 probe in 'RecRoyale_Frontier': … logs instance counts
for CircuitsV2Manager, RoomElementSettingsManager, RoomSkydome,
RoomSun, RoomFog, RoomBackgroundObjects and MakerPen so the effect
of the blob can be verified in one join.
How the ClientMod uses this
DorkNet.ClientMod/RecRoyaleNight.cs:
OnSceneWasLoaded→ scene name contains anyRecRoyaleNightScenesentry (defaultRecRoyale) ⇒ arm.OnUpdateevery 30 frames:RecRoomSceneManager.Instance.sceneMoodSettingspresent andCurrentMoodnon-null (engine initialised) ⇒Resources.Load(RecRoyaleNightMood, MoodSetting)⇒MLLBALMKEGJ(mood). Re-asserted every ~10 s (the engine re-applies its own mood on quality-level changes).- Hotkey (
RecRoyaleNightToggleKey, aUnityEngine.KeyCodename) toggles; off re-appliesCurrentMood, i.e. the scene's own default. - Harmony postfix on
get_FlashlightAllowedreturnstruewhile active and in a Rec Royale scene (RecRoyaleNightFlashlights).
Limits: baked lightmaps are untouched (interiors keep their daytime bake); darkening comes from the mood's colour grading, sun and ambient settings. Purely per-client — other players see whatever their own client applies.