You are viewing a potentially older version of this package. View all versions.
SpongeMods-SpongePEAKOptimized-1.0.0 icon

SpongePEAKOptimized

Fixes and optimizations in one mod, each individually toggleable. Includes disconnected-player persistence and rejoin handling.

Date uploaded 7 hours ago
Version 1.0.0
Download link SpongeMods-SpongePEAKOptimized-1.0.0.zip
Downloads 61
Dependency string SpongeMods-SpongePEAKOptimized-1.0.0

This mod requires the following mods to function

BepInEx-BepInExPack_PEAK-5.4.75301 icon
BepInEx-BepInExPack_PEAK

BepInEx pack for PEAK. Preconfigured and ready to use.

Preferred version: 5.4.75301
SpongeMods-SpongePEAKCore-1.0.0 icon
SpongeMods-SpongePEAKCore

Shared foundation for Sponge PEAK mods: logging, config, reflection caching and patch hosting. Not a standalone mod.

Preferred version: 1.0.0
SpongeMods-SpongePEAKLib-1.0.0 icon
SpongeMods-SpongePEAKLib

Modding API for PEAK, and a Mods tab in the game's own settings menu for toggling any installed mod's features.

Preferred version: 1.0.0

README

SpongePEAKOptimized

Fixes and performance work for PEAK, bundled into one mod with a toggle for every feature.

Instead of installing eight single-purpose mods that each patch the game separately, this is one plugin where overlapping behaviour is resolved once and deliberately.

Features

Every feature has its own setting in BepInEx/config/com.sponge.peakoptimized.cfg, so if one misbehaves you can switch off just that one.

Feature Default What it does
AdaptiveNetwork on Measures each player's connection and lowers the room's send rate when someone is struggling, so weak connections stop rubber-banding. Host decides, clients match.
AudioFix on Stops audio dropouts by parking silent looping sounds and prioritising voice and nearby audio.
CarryDisconnectedPlayers on Lets you pick up and carry teammates who disconnected while alive, and marks them (Disconnected). Ghosts cannot be carried.
DecorationCulling on Hides small decorative meshes beyond a distance, spreading the work across frames.
DistanceCulling on Skips dynamic-bone secondary motion beyond a distance from the camera, and caps its update rate.
GrassAllocations on Reuses one MaterialPropertyBlock for grass instead of allocating a new one every frame.
GrassFrustumCulling on Skips grass chunks outside the camera frustum. Grass casts no shadows, so this cannot change what you see.
KeepDisconnectedPlayersInWorld on Keeps a disconnected player's character alive and simulated until they rejoin, die, or the timeout expires. Room settings are applied when you host; every client tracks who is away.
LateJoinFix on Warps players who join mid-run to the party member furthest from the summit, then revives them.
LightSampleCache on Caches baked light volume samples, which cost a CPU 3D-texture readback per call.
LobbyJoinFix on Retired. PEAK now defers lobby invites until the session is connected, so this is no longer needed.
NullGuards on Stops two vanilla per-frame exceptions: pocket updates before a player exists, and particle culling with no observed character.
PerformanceLOD on Throttles physics updates for distant characters and avoids redundant rigidbody writes.
PhysicsThrottle on Caches wind shelter raycasts and removes the per-search scene scan from bot vision.
PlatformTuning on Applies the few engine settings that genuinely differ under Proton/Wine versus native Windows. Does nothing on Windows unless explicitly configured.
ProximityGating on Skips per-step updates for traps and ambient particle spawners with no player nearby.
ReconnectCatchup on Warps returning players to the party member furthest from the summit instead of the level start.
RejoinVoiceFix on Resets Photon Voice when leaving a game so voice chat works in the next lobby.
RestoreRejoinPosition on Returns a rejoining player to where they disconnected, if the gloom has not risen past it.
RopeShooterFix on Fixes the vanilla rope cannon refusing to fire: a single frame of lost footing or aim wobble cancelled the whole cast. Also stops spare cannons raycasting every frame.
SoftDependencyFix on Keeps partially-loaded assemblies usable, so a mod with a missing optional dependency does not hide unrelated console commands.
Telemetry off Shows frame time and what each optimization is doing, so the effect can be measured.
VineClimbFix on Stops rubber-banding when climbing vines and chains.
VisualQuality on Trades visual fidelity for frames: LOD bias, shadow bias and directional shadow tuning.
ZeroAllocHotPaths on Removes per-frame allocations from falling-rock physics and player proximity checks, reducing GC micro-stutter.

Disconnected players

The headline feature. Normally a player who drops has their character deleted instantly, so you cannot help them.

With this installed by the host, a disconnected player's character stays in the world: afflictions keep ticking, hazards still affect them, and teammates can pick them up and carry them to safety even though they are not incapacitated. When they rejoin they take their character back. If they die while away they become a normal ghost, and ghosts cannot be carried.

Two things worth knowing:

  • The host decides. Room settings come from whoever creates the lobby, so if the host has this mod, every player sees the behaviour, mod or not. If the host does not, it is inactive regardless of what clients have installed.
  • An absent player holds a slot until the timeout expires (default 5 minutes, configurable). That is the cost of keeping their character alive.

Compatibility

This mod covers the same ground as several existing mods. Running both means two mods acting on the same thing, so disable these if you have them:

Mod Overlap found
Cryo-PEAK_VineFix patches the same method, CharacterVineClimbing.RPCA_SyncVineClimb
KinTheInfinite-BetterFPS patches the same FixedUpdate methods on character physics
IceMods-PeakLobbyJoinFix targets OnLobbyJoinRequested; see the note below
Sapphire009-LateJoinFix sends the same WarpPlayerRPC and RPCA_ReviveAtPosition on join
Ayzax-Reconnect_Catchup sends WarpPlayerRPC on join, and warps every joiner
PatchNote-PeakAudioFix same audio-source prioritisation and pausing behaviour
PatchNote-RejoinVoiceFix both reset the Photon Voice client when leaving a game
PEAKModding-SoftDependencyFix same ReflectionUtility type-loading fix
reis637-PeakOptimizer patches the same ShakyRock, DynamicBone and PlayerHandler hot paths
LIGHTNING_X-CubeX partial: its bundled "Better FPS" patches the same CharacterRagdoll and CenterOfMass methods as PerformanceLOD. The rest of CubeX (its menu and tools) does not conflict. Disable CubeX's Better FPS options, or turn off PerformanceLOD here.

How this was determined: by comparing the game methods and Photon RPCs each mod touches against the ones this mod patches, using the installed binaries. The first three are direct patch collisions on identical methods. The rest are behavioural: different mechanism, same effect on the same game state.

The worst pairing is LateJoinFix + Reconnect_Catchup, which conflict with each other even without this mod: both warp a joining player, so two warps race for the same character. Resolving that is a large part of why this bundle exists. Here a single arbiter classifies each join once and dispatches to exactly one handler.

Not tested running side by side with these mods in a live session; the overlap above is from binary and behavioural comparison, which is why the guidance is to disable rather than to expect a specific failure.

A note on LobbyJoinFix

Investigated and deliberately not implemented. PEAK's own GameBooter now defers a pending invite until ConnectedAndReady:

if (!NetCode.Session.IsConnectedAndReady)
    NetCode.ConnectionEvents.ConnectedAndReady += JoinLobbyAfterRelayReady;
else if (NetCode.Matchmaking.HasPendingInvite)
    NetCode.Matchmaking.ConsumePendingJoin(andJoin: true);

That is the same defer-and-replay the mod added, so reimplementing it would race the game's own logic. IceMods-PeakLobbyJoinFix also targets SteamLobbyHandler.OnLobbyJoinRequested, which has since moved to Peak.Network.SteamLobbyAPI.

Prior art

Independent implementation, written against PEAK's own assemblies. The problems it solves were identified by earlier mods listed above, and this bundle exists because those solutions overlapped and sometimes fought each other.

Differences worth noting:

  • One arbiter decides how a joining player is handled, so late-join, position-restore and catch-up cannot race.
  • Per-feature toggles with failure isolation: a feature whose patches fail to bind is reported by name rather than silently counted as working.
  • SoftDependencyFix is an IL transpiler over a single Assembly.GetTypes() call, recovering the types that did load instead of discarding the whole assembly.
  • Disconnected-player persistence, carrying disconnected teammates, rejoin position restore and the mod handshake have no upstream equivalent.

Adaptive networking

If one player is on a weak connection, everyone feels it as rubber-banding and characters running in place. This measures each player's connection and adapts the room to suit the weakest one.

  • No extra traffic. It reads latency, jitter, packet loss and send-backlog counters that Photon already maintains. It does not send ping packets, which would burden exactly the connections that can least afford them.
  • Jitter matters more than ping. A steady 250ms connection plays fine; a 90ms one that spikes to 400ms is what actually causes rubber-banding, and that is what this reacts to.
  • The host decides one rate and everyone matches it, because the game interpolates other players against the local send rate. A client changing its own rate would cause the very stutter this removes.
  • Nothing happens if nobody is struggling, and players without the mod are never counted as bad, so they cannot drag the room down.
  • Everyone in the lobby needs the mod for the rate to change. The game interpolates other players against the local send rate, so a vanilla player left at 30Hz while the room sends slower would stutter. If anyone lacks it, the room stays at vanilla 30Hz.

Works for satellite, Starlink, tethered mobile, congested Wi-Fi and long-haul connections.

Turn on Telemetry (F6) to see your own latency and jitter, the room's current send rate, and each player's connection quality, so you can tell whose link is actually struggling.

Proton and Linux

PEAK is a Windows build, so under Proton it reports itself as Windows and ordinary platform checks cannot tell the difference. This mod detects the real situation (via the wine_get_version marker that Wine exports and Windows does not) and applies the few settings that genuinely differ:

  • Background loading priority is raised under Proton only, so more DXVK shader-pipeline compilation happens during loading screens instead of stuttering during play.
  • Everything else is unchanged, because the rest of the optimizations remove work from hot paths, which helps identically on every platform.

On native Windows this does nothing unless you configure it. The detected platform is written to the log, which is worth including in any performance report.

Requirements

BepInEx, SpongePEAKCore and SpongePEAKLib. r2modman installs these automatically.

Performance features absorbed from PeakOptimizer

Feature Default What it does
Light sample cache on The baked light volume is sampled with a CPU texture readback, every frame, per speaking player and per character in a wind zone. Cached.
Physics throttle on Wind shelter raycasts are cached (terrain does not move), and bot vision no longer scans the whole scene each search.
Decoration culling on Small scenery stops drawing at distance. Only small meshes, and only the draw is disabled, so nothing you can stand on ever disappears.
Grass allocations on Grass allocated a property block every frame; now reuses one.
Grass frustum culling on Grass behind the camera was still drawn. It casts no shadows, so skipping it cannot change the image.
Proximity gating on Citadel traps stop raycasting every physics step when nobody is near them.
Null guards on Fixes two vanilla exceptions thrown every frame: pocket updates before a player exists, and particle culling in menus.
Visual quality off LOD and shadow tuning. Trades looks for frames, so opt-in. PEAK_Visuals is better at this.
Telemetry off Frame time and cull counters, so the above can be measured rather than assumed.

Conflicts with reis637-PeakOptimizer. Both patch the same methods. Run one or the other. Its experimental GPU occlusion culler is not reimplemented here: it is disabled by default even in that mod, and marked use-at-your-own-risk. Run PeakOptimizer if you specifically want it.

License

Free to use, stream, and include in modpacks. No attribution needed for any of that.

Attribution is only required if you decompile it and reuse the code. Please do not reupload it elsewhere or sell it.

Full terms in LICENSE.

Keybinds

Key Opens Setting
F8 Telemetry overlay (feature is off by default) TelemetryToggleKey

All rebindable, in the config file or in Settings > Mods.

Configuration

Every setting is in BepInEx/config/com.sponge.peakoptimized.cfg, or in the game's own Settings > Mods tab.

AudioFix

Setting Type Default What it does
ScanInterval number 0.5 Seconds between audio source scans. Lower reacts faster but costs more CPU; FindObjectsByType is not cheap.

Diagnostics

Setting Type Default What it does
TelemetryInterval number 0.5 Seconds between readout refreshes.
TelemetryToggleKey key F8 Key that shows or hides the readout while the feature is enabled.
Telemetry on/off false Shows frame time and what each optimization is doing, so the effect can be measured.

Fixes

Setting Type Default What it does
VineClimbFix on/off true Stops rubber-banding when climbing vines and chains.
AudioFix on/off true Stops audio dropouts by parking silent looping sounds and prioritising voice and nearby audio.
RejoinVoiceFix on/off true Resets Photon Voice when leaving a game so voice chat works in the next lobby.
LobbyJoinFix on/off true Retired. PEAK now defers lobby invites until the session is connected, so this is no longer needed.
SoftDependencyFix on/off true Keeps partially-loaded assemblies usable, so a mod with a missing optional dependency does not hide unrelated console commands.
RopeShooterFix on/off true Fixes the vanilla rope cannon refusing to fire: a single frame of lost footing or aim wobble cancelled the whole cast. Also stops spare cannons raycasting every frame. (replaces (vanilla bug))
NullGuards on/off true Stops two vanilla per-frame exceptions: pocket updates before a player exists, and particle culling with no observed character.

Network

Setting Type Default What it does
AdaptSendRate on/off true Lower the room's network send rate when a player's connection cannot keep up. Host decides; clients follow the host so everyone's interpolation matches. Off means always use the vanilla 30Hz.
SampleIntervalSeconds number 5 How often to read connection quality. This costs no packets: it reads counters Photon already maintains, so a short interval is cheap and reacts sooner.
MinimumSerializationRate number 15 Lowest serialisation rate the host may drop to, in Hz. Vanilla is 30. Below about 15 the game's own interpolation starts to look obviously soft.
LogConnectionQuality on/off true Log connection tier changes and rate adjustments. Useful for diagnosing whose connection is actually causing trouble.
AdaptiveNetwork on/off true Measures each player's connection and lowers the room's send rate when someone is struggling, so weak connections stop rubber-banding. Host decides, clients match. (replaces )

Performance

Setting Type Default What it does
NearUpdateRate number 60 Physics updates per second for nearby remote characters. 0 disables their updates entirely.
MediumUpdateRate number 30 Updates per second for characters past MediumDistance.
FarUpdateRate number 10 Updates per second for characters past FarDistance.
MediumDistance number 30 Distance (metres) at which a character drops to MediumUpdateRate.
FarDistance number 60 Distance (metres) at which a character drops to FarUpdateRate.
ZeroAlloc_ShakyRock on/off true Replaces allocating physics queries and leftover debug logging in falling-rock updates.
ZeroAlloc_ProximityChecks on/off true Uses squared-distance comparison instead of Vector3.Distance for player proximity checks.
CullDistantDynamicBones on/off true Skips dynamic-bone updates beyond DynamicBoneDistance from the camera. Purely visual.
DynamicBoneDistance number 40 Metres from the camera beyond which dynamic bones stop updating. Lower is faster; too low shows stiff foliage nearby.
CapDynamicBoneUpdateRate on/off true Caps dynamic-bone update rate at 30 Hz instead of the default 60.
LightSampleGridSize number 1 World-space cell size for cached light samples, in metres. Smaller is more accurate and hits the cache less often.
LightSampleCacheEntries number 4096 Maximum cached samples. The cache clears wholesale when full, which is cheaper than tracking per-entry age for a value this cheap to recompute.
WindShelterInterval number 0.1 Seconds between wind shelter raycasts per character. Terrain does not move, so this only needs to keep up with the player.
OptimizeBotVision on/off true Replaces the scene scan in bot vision with the list the game already maintains.
DecorationCullDistance number 90 Distance beyond which small decorations stop being drawn.
DecorationMaxExtent number 1.5 Largest bounds extent, in metres, that counts as a decoration. Anything bigger is left alone, so climbable geometry is never hidden.
DecorationChecksPerFrame number 64 How many decorations to test each frame. Lower spreads the cost further.
TrapActiveDistance number 80 Distance within which traps keep updating. Well beyond their own reach, so this cannot stop a trap that could actually hit someone.
ReportPlatform on/off true Log which platform was detected and the evidence for it. Useful when reporting a performance problem, since Proton and Windows behave differently.
ProtonBackgroundLoadingPriority number 2 Proton/Wine only. How much time per frame the engine may spend on background loading, which is when DXVK compiles shader pipelines. 0 leaves the engine default, 1 is low, 2 is normal, 3 is high. Higher trades slightly longer loads for fewer first-encounter stutters. Ignored on native Windows.
MaxQueuedFrames number -1 Frames the CPU may queue ahead of the GPU. -1 leaves the engine default (2). Setting 1 can reduce input latency under DXVK, at some risk to throughput. Applies on any platform, because it is a preference rather than a fix.
PerformanceLOD on/off true Throttles physics updates for distant characters and avoids redundant rigidbody writes.
ZeroAllocHotPaths on/off true Removes per-frame allocations from falling-rock physics and player proximity checks, reducing GC micro-stutter. (replaces reis637-PeakOptimizer (partial))
DistanceCulling on/off true Skips dynamic-bone secondary motion beyond a distance from the camera, and caps its update rate. (replaces reis637-PeakOptimizer (partial))
LightSampleCache on/off true Caches baked light volume samples, which cost a CPU 3D-texture readback per call.
PhysicsThrottle on/off true Caches wind shelter raycasts and removes the per-search scene scan from bot vision.
DecorationCulling on/off true Hides small decorative meshes beyond a distance, spreading the work across frames.
GrassAllocations on/off true Reuses one MaterialPropertyBlock for grass instead of allocating a new one every frame.
GrassFrustumCulling on/off true Skips grass chunks outside the camera frustum. Grass casts no shadows, so this cannot change what you see.
ProximityGating on/off true Skips per-step updates for traps and ambient particle spawners with no player nearby.
PlatformTuning on/off true Applies the few engine settings that genuinely differ under Proton/Wine versus native Windows. Does nothing on Windows unless explicitly configured. (replaces )

Rejoin

Setting Type Default What it does
LateJoin_AfterWarpDelay number 2 Seconds between warping a late joiner and reviving them. Too short and the revive can land at their pre-warp position.
LateJoin_WarpForwardOffset number 2 How far (world forward) to offset the warp from the target player, so the two do not spawn inside each other.
LateJoin_ReviveHeightOffset number 3 How far above the target player to revive the late joiner.
CatchUp_HeightOffset number 1 Metres above the target player's head to place a returning player.
Restore_FogSafetyMargin number 10 Metres of clearance required between the gloom and a player's saved position before restoring them there.
Restore_MaxAwayTimeSeconds number 300 Longest disconnect, in seconds, that still restores the original position. Past this they are caught up to the party instead.
DisconnectedPlayerTimeoutSeconds number 300 How long a disconnected player's character stays in the world before Photon reclaims it. They occupy a player slot for this whole period, so keep it in line with your player cap. Only applies to lobbies you host.
LateJoinFix on/off true Warps players who join mid-run to the party member furthest from the summit, then revives them.
RestoreRejoinPosition on/off true Returns a rejoining player to where they disconnected, if the gloom has not risen past it. (replaces (new behaviour))
ReconnectCatchup on/off true Warps returning players to the party member furthest from the summit instead of the level start.
CarryDisconnectedPlayers on/off true Lets you pick up and carry teammates who disconnected while alive, and marks them (Disconnected). Ghosts cannot be carried. (replaces (new behaviour))
KeepDisconnectedPlayersInWorld on/off true Keeps a disconnected player's character alive and simulated until they rejoin, die, or the timeout expires. Room settings are applied when you host; every client tracks who is away. (replaces (new behaviour))

Visuals

Setting Type Default What it does
LodBias number 1 Level-of-detail bias. Below 1 swaps to simpler meshes sooner: more frames, more visible popping. 1.0 leaves it alone.
TuneShadowBias on/off true Adjusts directional light shadow bias to reduce acne, which allows a lower shadow distance without obvious artefacts.
ShadowDistance number 0 Maximum shadow draw distance in metres. 0 leaves the game's value alone.
VisualQuality on/off true Trades visual fidelity for frames: LOD bias, shadow bias and directional shadow tuning.

CHANGELOG

Changelog

1.0.0

First public release. Nothing before this was ever published, so everything below is new rather than a change from a version anyone has installed. The entries are kept in the detail they were written in, because most of them explain a real bug in the game or in these mods and that reasoning is worth more than a one-line summary.

  • Three of the four example items were cloning an item that does not exist. They named their source "Banana". PEAK's fruit is a Berrynana, and each colour is a separate prefab, so the lookup correctly found nothing and those clones were dropped. Only Nuclear Dynamite ever appeared, which is why the spawner said "1 custom".

    Source names refer to prefab assets rather than code, so a wrong one compiles fine and passes every assembly-level test. Two things now close that gap: tools/extract-item-names.py derives the real item list from the game's own asset files, and tools/check-item-sources.py fails the build if any clone names a source that does not exist, suggesting near matches. A clone can also declare fallback source names now, so a game update renaming a prefab degrades instead of silently deleting the item.

    The lookup also now ignores spaces and underscores, because PEAK's prefab and display names disagree about them constantly ("Airplane Food" versus "Airline Food").

  • Custom items were being lost to a load-order race. Three of the four example clones never got built, and the item spawner reported "1 custom" instead of 4.

    BepInEx loads plugins alphabetically, so SpongePEAKCheats initialises before SpongePEAKQOL. The clone builder ran as soon as ItemDatabase.Instance was non-null, which Cheats triggered with an empty queue, marking the build as done. Every later registration then built immediately, at plugin-load time, when the database object exists but its item lookup is still empty. Every source item resolved as missing, and the warning blamed the item name rather than the timing.

    Building now waits for the lookup to actually contain items, and a failed lookup lists similarly-named items so a genuine typo is obvious instead of looking like this bug.

  • The lobby browser's "YOUR LOBBY" heading rendered as a vertical column of letters. The label helper sizes for a stretched anchor, where the width is an inset from the panel edges, and the heading re-anchored to a point without setting its own width. The inset then became a literal negative width, so the text wrapped at one character per line.

  • Nuclear Dynamite is 200x, and its force on objects now actually works. The blast, damage, knockback and object force are all 200x vanilla, up from 20x. Radius 5 becomes 1000 world units; expect a visible hitch on the frame it detonates, which is affordable because AOE.Explode runs its overlap query once at detonation rather than every frame.

    Two bugs were also hiding behind each other. The game computes an object's impulse as (dir * knockback + up * verticalKnockback) * factor * itemKnockbackMultiplier, so knockback is already in the product; scaling the item multiplier by the requested force as well multiplied the two, which at these numbers would have been 40000x. The knockback is divided back out, so the item-force number means the force on objects relative to vanilla.

    Meanwhile none of it did anything unless AOE.canLaunchItems is true, which vanilla leaves false on some explosions, silently discarding every force value. It is enabled when a force above 1x is requested.

  • Everything is on by default now, except two things. VisualQuality and LobbyJoinFix were the last features shipping disabled. VisualQuality is on because trading a little fidelity for frames is what an optimisation mod is for, and its defaults are mild with every knob exposed. LobbyJoinFix is on because it installs no patches at all any more, so there is nothing left for it to race; enabling it is a no-op.

    The two deliberate exceptions: Telemetry, which draws a diagnostic overlay over the game, and listing your lobby publicly, which makes you visible to strangers.

  • The lobby browser works on the main menu, and has a button there. A LOBBY BROWSER entry now sits beside the vanilla menu buttons, and F6 works there too. The menu is where you are choosing a game to join, so it is the browser's most useful location; joining takes a Steam lobby ID and never depended on being in a run.

  • The mushroom effect list stays blank outside a run, unavoidably. MushroomManager is a level generation step, so which mushroom does what is randomised when the level is built. Before a run there is no table to show, and showing the last run's would be confidently wrong. Its hotkey is ignored on the menu so it cannot be toggled into a state that surprises you when the run starts.

  • Telemetry moved from F6 to F8. F6 was bound twice, by Telemetry and the lobby browser, so one key did two things. It was only latent because Telemetry shipped disabled; making it enable-able exposed it. F7 was already taken by the mushroom readout.

  • SpongePEAKAll, a modpack. One install that pulls in all seven mods at matching versions, so nobody has to add them one at a time or work out the dependency order. It ships no code of its own, only dependencies; a modpack that also shipped a DLL would install that DLL twice.

  • Custom items now actually appear. They were registered at startup and then never built, so the item spawner listed none and reported "no custom items: enable them in Mods settings" even though the feature was enabled. The build step hung off a Harmony patch on ItemDatabase.LoadItems, which exists, is public, and patches cleanly, but has an empty body and is never called by the game. Nothing reported an error because nothing was wrong at the patching level; the postfix simply never ran.

    Clones now build from ItemDatabase.OnLoaded, which the asset singleton really does invoke, with a direct build on startup as well in case the database was already resolved. The spawner's message now distinguishes "registered but not built" from "none registered", so those two very different problems can no longer look identical.

    A new gate rejects any Harmony patch whose target method has an empty body, which catches this exact class of silently-dead patch. It was confirmed to fail on the original bug before being kept.

  • SpongePEAKOptimized: the rope cannon fires again. This is RopeShooter, the cannon that shoots an anchor at a wall, not the rope spool you unwind by hand. RopeShooter.Update recomputes item usability from a raycast every frame, and the game cancels an in-progress cast the moment usability goes false, resetting cast progress to zero. Since the check requires both isGrounded and a terrain hit, and both flicker for a frame on slopes, steps, and ordinary mouse movement, the cast kept restarting. Usability is now latched for the duration of a cast; placement still re-validates your aim, so nothing can be placed where vanilla would refuse.

    Not confirmed in a live run. The mechanism comes from the decompiled game, but the report that prompted it was about the rope spool, which is a different item and is unaffected.

    The same fix removes a per-frame physics raycast for every rope cannon in the world, including ones nobody is holding, which vanilla paid for every frame.

  • All mod windows now use PEAK's own UI, and the mouse cursor works. Previously every window was drawn with Unity's IMGUI and set the cursor itself, which never worked reliably: CursorHandler re-locks the pointer every frame unless the game believes a menu is open, so the game and the mod each wrote the cursor every frame and the game usually won. The windows appeared and could not be clicked.

    The item spawner, run rules, name prompt, and lobby browser are now built from PEAK's own button and slider prefabs, inherit the game's fonts, and register as real MenuWindows so the game grants the cursor through its own path and stops routing clicks to the held item. They also look like the rest of the game rather than like a debug overlay.

  • Settings lists that are too long now scroll. PEAK's settings UI has no scroll view at all, because vanilla tabs never overflow, so a mod adding enough entries simply pushed them off the bottom of the screen where they could not be reached. Our tab now builds its own viewport and scroll view.

  • Run rules are reachable in-game. A RUN RULES button in the pause menu opens the host settings; clients see the values greyed out with a note that the host controls them.

  • Renaming yourself no longer overlaps the passport number. The rename control sits beside your name instead of on top of the number.

  • SpongePEAKLobby: a new mod. A lobby browser with lock codes. PEAK only ever creates friends-only or private Steam lobbies, so a browser mod is what makes a lobby publicly listable at all. That is normally all-or-nothing. A lock code is the setting in between: set one on your lobby and it disappears from every lobby browser, including this one, while staying joinable by Steam invite, from your friends list, and by anyone who types the code. So a friend can bring a friend and nobody browsing a public list ever finds you.

    It hides for a real reason rather than a cosmetic one: Steam does the filtering on its own servers, so a browser searching for public lobbies never receives a locked one. Codes ignore case and surrounding spaces, and are stored hashed rather than in plain text. If the host leaves, the promoted host re-asserts the lock so the lobby does not quietly become visible.

    "Hidden" means hidden from browsers, not invisible to your own Steam friends, and it is obfuscation rather than security. The mod says so plainly instead of implying otherwise.

  • SpongePEAKQOL: rope swinging, pushing, and rope cutting. All three are host-controlled, because opting out of being pushed is not something a client can decide for itself, so the host decides once for the lobby.

    All three are on by default, like everything else in the family. Everyone in a lobby installed the same pack on purpose, so the opt-out is choosing not to play with that group, and shoving your friends off a mountain is the point rather than a hazard. Where a real accident is possible it is prevented in code instead: cutting refuses a rope that someone is currently on. The host can still switch any of them off for the lobby.

    Swinging raises the rope angle limit the game uses to drop you, and deliberately adds no force of its own: the momentum is the one the game's physics already produce. Pushing sends the game's own force RPC on the target's view, so their client applies it and authority stays where the game put it. Cutting requests Photon ownership before destroying, since a non-owner's destroy is simply refused, and it refuses ropes with someone on them unless you turn that off.

  • SpongePEAKTweaked: campfire safe zone. Near a lit campfire, stamina stops draining, and optionally one resting player is enough to hold the gloom back rather than requiring the whole group, so a scout going ahead does not restart the timer for everyone at camp. Host-controlled and on by default: it only ever helps, and it helps everyone equally.

    Note that the game already stops hunger and already pauses the gloom at a campfire, so this deliberately adds only the parts that were genuinely missing rather than reimplementing behaviour that ships with PEAK.

  • SpongePEAKOptimized: adaptive networking. Measures every player's connection quality from telemetry Photon already maintains (latency, jitter, retransmissions, send backlog) without sending any probe packets, and lowers the room's send rate when someone cannot keep up, so a struggling player stops rubber-banding for everyone. The host decides one rate and clients match it, because the game interpolates remote players against the local serialisation rate. Nothing changes when nobody is struggling, players without the mod are never counted as the weakest link, and the rate only moves when every player in the room has the mod, so nobody is left interpolating against a rate they cannot follow.

  • SpongePEAKOptimized: Proton/Wine aware tuning. PEAK is a Windows build, so under Proton it reports itself as Windows and normal platform checks cannot tell. Detection now uses the ntdll!wine_get_version marker (read via the MonoMod that BepInEx already ships, so no native interop is added). Under Proton, background loading priority is raised so more shader pipeline compilation happens during loads rather than as first-encounter stutter. On native Windows nothing changes unless configured.

  • SpongePEAKLib: a "Mods" tab in the game's own settings menu. Lists the on/off settings of every installed BepInEx mod, rendered with the game's own settings rows rather than a custom window. Changes write to each mod's own config file. Rows changed this session are marked "restart to apply", because mods install their patches at startup. Core and Lib are marked "required" and cannot be disabled from the panel, since Lib draws it.

  • SpongePEAKTweaked: change your in-game name from the passport. A [ change name ] control sits under your name on the passport; the passport number updates with it. The name persists across restarts and is visible to players without the mod. On by default but inert: until you choose a name your Steam name is used, untouched. Per-player rather than host-controlled.

Foundations

  • Three-tier split: Core (shared foundation), Lib (modding API), Optimized (fixes bundle).
  • Eleven independently toggleable features in SpongePEAKOptimized.
  • Disconnected players stay in the world, can be carried to safety while alive, and are restored on rejoin when the gloom has not risen past where they left.
  • Single join arbiter so late-join, position-restore and catch-up never race.
  • Mod handshake over Photon player properties for host/client capability checks.