You are viewing a potentially older version of this package. View all versions.
khalil2-PeakSafeOptimizer-1.5.1 icon

PeakSafeOptimizer

Performance mod for PEAK. 37 patches, all on by default: equivalence-proven rewrites or vanilla crash fixes, each with fail-closed member validation, plus a switch for every one.

By khalil2
Date uploaded 5 days ago
Version 1.5.1
Download link khalil2-PeakSafeOptimizer-1.5.1.zip
Downloads 138
Dependency string khalil2-PeakSafeOptimizer-1.5.1

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

README

PeakSafeOptimizer

A performance mod for PEAK aimed at the case that actually hurts: a full lobby standing in one place.

Most of what it does is invisible. Most of its 37 patches are equivalence-proven - they produce bit-for-bit the same result as the vanilla code, just with less work - or they repair a vanilla fault that throws an exception every frame. A handful trade a little appearance or animation fidelity for frames; those are called out under the switches below. Every patch is on by default, so a fresh install is already the configuration this release was audited and played in.

There are no numbers to tune. Every threshold is a constant in the code, next to the reasoning that picked it.

This mod has moved to a new Thunderstore account. The account that owns the old page is bound to a GitHub account that has been banned, and Thunderstore sign-in goes through GitHub, so that account can no longer be logged into and the old page can no longer be updated. It stays frozen at 1.4.1. Publishing continues under khalil2 - this release and every one after it. If you installed from the old page, switch to the new one to keep getting updates.

Links

Installation

Mod manager (recommended): click Install. BepInEx is pulled in automatically.

Manual: install BepInExPack PEAK first, then drop PeakSafeOptimizer.dll into PEAK/BepInEx/plugins/.

Works in multiplayer with vanilla players and with players who do not have the mod. Nothing it does is networked - every patch acts only on your own client's workload.

What it actually does

Removes per-frame exception floods. PEAK throws a few NullReferenceExceptions every single frame in situations that are easy to hit (rope climbing with an empty segment list, an incomplete local character while the emote wheel is up, hand IK during an item swap, two unguarded singletons while binoculars are raised). An exception per frame is not free. Seven guards cut those to zero - each one skips exactly the frame whose data is missing and defers to the untouched original otherwise, and each one mirrors the short circuits in the original so it never skips a frame vanilla would have survived. Those seven are independent switches, all in the stability lane. Measured in one session: 2315 + 1544 + 184 + 135 + 67 exceptions. Those counts predate the current guards and the emote-wheel figure in particular has not been re-measured against them, so treat them as the order of magnitude rather than the exact win. The binocular one alone was 1544 - one per frame for as long as anyone held the binoculars up, each costing a throw, a stack capture and a synchronous log write on the main thread.

Cuts redundant native calls. A large part of the per-frame cost in a multiplayer lobby is the game writing values that are already what it is about to write, or recomputing something it computed a moment ago. Examples: Bodypart.Drag multiplies two velocity vectors by exactly 1f for every bodypart of every ragdolled character, 50 times a second; Bodypart.AddMovementForce adds the zero vector for everyone who is not pressing a movement key; the LightVolume lightmap is sampled per character per frame for voice occlusion when clustered players share the same voxel. These are skipped only when the skip is provably an identity.

Replaces scans and lookups with cheaper equivalents. Squared distances instead of square roots, string Animator parameters hashed once instead of per frame, a dictionary instead of a linear search, one full player scan instead of two in the same frame.

Reduces garbage. CharacterMovement.OnCollision evaluated the allocating Collision.contacts property six times per contact; it now reads contact zero once. The engine is also told to stop allocating a Collision object per reported collision at all - see the switches below.

Scales down explosions. Vanilla spawns 10 orbs per dynamite explosion, each an animated transparent sphere roughly twelve metres across. This caps a single explosion at 5 orbs at its authored size, and when explosions pile up in the same place it drops the count to 2 and shrinks the orbs to 60%. With ten sticks detonating inside one metre the frame is spent filling the screen several times over rather than on spawning orbs, so the count reduction alone barely moved it - the shrink is there for the fill, though how the cost splits between the orb mesh and the two particle systems each orb also carries has not been measured. A clustered explosion therefore looks smaller per orb, but you are seeing the union of many of them, so the fireball stays wider than any single blast. A lone explosion is not shrunk at all. Two things follow that are worth knowing: fewer orbs also means a softer camera shake, because each orb fires two screen shakes and the game stacks them; and this only covers dynamite - the rocket, rocket-pack and ghost-ball explosions are different prefabs whose cost is in particle systems, and they are untouched. Damage, knockback and blast radius are unaffected either way - those live in a separate component.

Switches that trade fidelity for frames

On by default, like everything else. Turn them off if you would rather keep the fidelity.

Switch What you gain What it costs
Experimental.ExplosionScale A single dynamite explosion spawns 5 orbs instead of 10; piled-up explosions drop to 2 and shrink the orbs to 60% A smaller-looking blast per orb and a softer camera shake, because each orb also fires two screen shakes that the game stacks. Damage, knockback and radius are untouched - they live in a separate component
Experimental.RemoteClusterAnimationThrottle Remote players evaluate their animation graph every other physics tick instead of every tick - roughly halves the single most expensive managed per-character cost in a cluster Remote animation steps at 25Hz. Speed is unchanged (the accumulated delta time is passed through), but the stepping is noticeable up close
Experimental.RemoteRagdollLod Remote players past 60m stop evaluating animation entirely and become passive ragdolls carried by physics and the network A distant player's pose freezes. Distance is measured from whoever you are watching, so spectating does not freeze the lobby around you. One switch covers four patches that are only coherent together, and they are installed as a unit
Experimental.CollisionCallbackReuse The engine stops allocating a Collision object per reported collision - the largest single source of collision garbage in a cluster A process-wide engine flag. The game side is fully audited (exactly one place keeps a collision across frames, and it is guarded), but the game also hands the live collision to mods through two public delegates, so another mod that holds one past its callback would start reading a reused instance
Graphics.CapBoneWeights Two bones per vertex instead of four or unlimited - a per-vertex cost of every character on screen, and the game offers no setting for it Joint deformation at shoulders and hips is a little coarser

Every switch hot-reloads: change it in a config manager and it installs or uninstalls immediately, no restart. A switch that covers several patches is installed all-or-nothing - if one member cannot install on your build, the others are withdrawn and the log says which and why. Both engine-level lanes put back the value they found when their own switch goes off and on game exit, and only while the value in force is still the one they wrote, so a mod that changed it afterwards keeps its own. The collision lane also restores it before any patch toggle, because the two guards it depends on can be removed there.

What it deliberately does not do

It does not override your graphics settings. Shadow distance, render scale, texture quality, LOD bias and anti-aliasing all belong to the in-game menu. An earlier version capped shadow distance and cascade count; that was removed, because it silently overrode a choice the player had already made - and raising the setting appeared to do nothing.

It contains no Transpiler. Every patch is a Prefix or Postfix with an exact structure check. IL rewriting is fragile in a heavily modded environment, and the one Transpiler that survived testing was dropped before release rather than shipped.

It does not touch networking. No Photon serialization rate, no RPC, no interpolation of remote positions. The one patch whose target is an RPC entry point is held to the strictest validation tier.

It does not fix other mods. Faults caused by other mods are out of scope.

Safety model

Each patch validates the game build itself before installing: declaring type, member type, exact signature, and for the enum-driven ones the member order. If anything does not match, that patch reports SkippedStructureMismatch and stays uninstalled. The others are unaffected.

Every patch is attempted on every build and each one decides for itself: it installs only if the exact member shapes it reads all match, and reports SkippedStructureMismatch otherwise. There is deliberately no build-level gate - an earlier version skipped almost the whole mod on any game update before a single patch got to check whether the members it actually reads had changed, which is backwards. What a shape check cannot see is a method body that kept its shape but changed meaning; the startup log therefore reports the game assembly's MVID so you can tell whether anyone has re-read that build. Patches classified Fingerprint or StrictIl in the log are the ones worth re-reading after an update. Nothing is ever installed on a "probably fine" basis.

A failure at any stage - factory, initialization, target resolution, Harmony patching - is isolated to that one patch and logged, or to that one switch when the switch covers several patches that are only correct together. Uninstall is per method and verified against Harmony's own patch info; UnpatchSelf is never called, so other mods' patches on the same methods are never disturbed.

Verifying it is working

Filter BepInEx/LogOutput.log for PeakSafeOptimizer. You should see:

PeakSafeOptimizer | compatibility mode: certified
PeakSafeOptimizer | totals: registered=37, enabled=37, applied=37, skipped=0, failed=0, module-failures=0

applied should equal enabled, and with a default config both are 37. A lower enabled means you turned a switch off. failed=0 and module-failures=0 mean no patch hit a problem.

compatibility mode: structural-validation instead of certified just means the running game build is not the one this release was last audited against. Every patch still validates and installs on its own merits; the label tells you to start re-reading from the Fingerprint and StrictIl entries.

If something goes wrong

Turn off the last switch you enabled. If you enabled nothing, the likely candidates in order are the two engine-level lanes, Experimental.CollisionCallbackReuse and Graphics.CapBoneWeights, then any of the throttles, then Experimental.ExplosionScale. Every switch is independent.

Diagnostics.DetailedLogging is on by default. It logs a line the first time each repair reason is hit and then at most once every 30 seconds per reason - it never spams per frame, and it only counts repairs and fallbacks, never the normal optimized path. Those lines are the fastest way to tell which guard is doing something.

A crash, a save/network/scene fault, or anything visually broken is worth reporting with the full log.

Compatibility

Built against PEAK with BepInEx 5.4.23.3 and tested alongside 20+ other mods. It patches only vanilla methods, so it does not conflict with content mods. Where the game itself writes a property every frame, this mod deliberately uses a different channel so the two cannot fight each other.

MIT licensed.

CHANGELOG

Changelog

1.6.0

  • Updated for game 3.0.0.
  • Removed EyeLookComponentCache. The game now does the same optimisation itself, and does it faster than this patch did.
  • Fixed AnimatedMouthMaterial, which could throw every frame on a remote player's mouth.
  • Added four missing structure checks, so a future game update drops the affected patch instead of misbehaving.

1.5.0

  • Updated for game 2.6.b. No patch needed changing.
  • Removed four patches that had stopped earning their place: DetailBodypartThrottle, TumbleWeedTargetScan, ItemScaleRedundantWrite, RemoteItemInterpolationThreshold.
  • Added two: ItemPromptDuplicateRefresh and InventoryNameTextRefresh. Both drop a refresh the same frame repeats anyway.
  • Fixed the lightmap cache serving the previous level's data after a load, and the mouth patch reading the push-to-talk setting before the game's own checks.
  • Every switch is on by default now. That is the configuration that has actually been played.
  • New icon.

1.4.1

  • Removed PocketBehaviorGuard, which had been guarding a null the game had already fixed, and AnimatorValuesHash, whose component does not exist in the game at all.
  • Fixed IKItemGuard skipping the wrong hand. For two releases it froze the left IK hand on items missing a left hand node.

1.4.0

  • Removed the "certified build" gate. It used to skip almost the whole mod on any game update, before a single patch got to check whether the things it reads had changed. Each patch now decides for itself.
  • Removed three patches the game had made redundant: GenericOptimizerRange, StatusEmitterScan, RagdollPhysicsMats.
  • Adapted four patches to the new build, and taught RemoteRagdollLod about characters carried by a condor.

1.3.0

  • Fixed IKItemGuard freezing the right IK hand on items that have a right hand node but no left one.
  • Fixed three bugs in the machinery that installs a multi-patch switch as a unit, which could leave half a switch installed with nothing in the log.
  • ExplosionScale's overlap radius is now derived from the shipped assets instead of being a hard-coded 3.5 m.
  • Fixed several repair counters that were counting normal operation as a repair.
  • Removed a per-call allocation and a handful of repeated native reads from hot paths.

1.2.0

  • Removed ParticleCountCullerWrite. Its component has zero instances in the shipped game, so the patch could only ever cost a wrapper.
  • Fixed EmoteWheelGuard being able to leave the emote wheel stuck open.
  • Corrected the orb numbers: an explosion is 10 orbs, not 13. Fewer orbs also means a softer camera shake, which is now declared.
  • A switch that covers several patches now installs all-or-nothing.
  • The two engine-level lanes now restore only their own value, and only while it is still the value they wrote.

1.1.0

  • Second audit pass: four patches fixed where the replacement did not quite match the original.
  • ExplosionScale now actually helps with a pile of dynamite, by shrinking the orbs as well as reducing how many there are.
  • BinocularSunGuard and EmoteWheelGuard widened to cover more of the null chain.

1.0.0

First release.