ReksikPerformance
FPS and Session Reliability for ROUNDS: adaptive VFX and lights, Photon diagnostics, fail-closed guards, and opt-in room-bound next-point rejoin.ReksikPerformance 0.3.1
ReksikPerformance treats Session Reliability and Squeeze FPS as equal product pillars for heavily modded, long-running ROUNDS sessions.
The performance side applies reversible client-local graphics and VFX budgets. The reliability side records bounded evidence, installs narrowly verified guards for known error storms, and offers an opt-in non-host hot-rejoin experiment. Experimental rejoin is disabled by default.
What this mod does
| Area | What it does |
|---|---|
| Local frame pacing | Applies a target frame rate and VSync interval. |
| Local graphics | Controls MSAA, shadows, shadow distance, pixel lights, anisotropic filtering, texture limit and LOD bias. |
| Local VFX budgeting | Caps particle systems, scales particle emission and trail lifetime, and moves overflow lights off expensive pixel/shadow rendering paths. |
| Adaptive control | Moves through T0-T3 only after sustained frame pressure and recovers one tier at a time after sustained headroom. |
| Live evidence | Shows an optional FPS, 1% low, frame-time, managed-memory, GC and tracked-VFX overlay. |
| Session diagnostics | Records bounded, privacy-safe Photon state, world counts, plugin inventory and error fingerprints. |
| Reliability guards | Patches only exact, audited method signatures and leaves unknown versions untouched. |
| Experimental rejoin | Makes at most two room-bound Photon rejoin attempts for an eligible non-host, then validates warm state at the next point boundary. |
What this mod does not do
ReksikPerformance does not:
- change damage, projectile simulation, card balance or authoritative game state to gain FPS;
- promise higher FPS when the bottleneck is third-party gameplay code, physics, networking, the GPU driver or another external limiter;
- pool arbitrary mod objects or rewrite every mod's update loops;
- reduce ping, repair packet loss, prevent every disconnect or eliminate desync;
- migrate a host, elect a replacement master client or reconnect the disconnected host;
- save and restore an arbitrary mid-point world;
- restore health from the pre-disconnect snapshot, running coroutines, card-owned transient components, spawned objects, current projectiles or pick-screen state;
- silently patch unknown mod versions when an audited runtime contract no longer matches.
Requirements
BepInEx-BepInExPack_ROUNDS-5.4.1901willis81808-UnboundLib-3.2.14
FPS profiles and diagnostics are client-local. Each player who wants those features installs the mod for themselves.
Experimental rejoin is different: the room creator and the reconnecting non-host client must both enable it before the room is created. A client cannot retrofit Photon TTLs onto an existing room.
In a lobby with additional peers, every peer that can run the audited player-left callbacks should use the same opt-in. There is no config handshake; an unconfigured third client can still perform destructive vanilla cleanup when it observes the inactive actor.
Quick start
- Open the UnboundLib mod settings menu.
- Open REKSIK PERFORMANCE.
- Keep Session Reliability enabled.
- Choose Balanced for a long modded session.
- Keep Adaptive VFX Pressure Control enabled.
- Enable the compact overlay only while tuning or troubleshooting.
- Leave experimental rejoin disabled unless you are deliberately testing with a host and a second client.
The menu is dark, refreshes from the real config values, and keeps every toggle visibly marked [ON] or [OFF].
Quick presets
These are recommended recipes, not additional values of the Profile enum.
| Goal | Recommended settings |
|---|---|
| Long modded session | Profile=Balanced, adaptive on, Session Reliability on, diagnostics on, guards on, experimental rejoin off. |
| Maximum uncapped local FPS | Profile=MaxFps, adaptive on, overlay temporarily on. |
| Weak GPU or extreme visual clutter | Profile=Potato, adaptive on. Potato uses a 90 FPS target but lowers texture and VFX cost more aggressively. |
| Competitive frame pacing | Profile=Competitive, adaptive on; adjust TargetFrameRate to a sustainable value if 240 is unrealistic. |
| Preserve presentation quality | Profile=Cinematic; adaptive can still lower VFX during sustained pressure unless disabled separately. |
| Observe a disconnect without interventions | Session Reliability on, diagnostics on, CommonDisconnectGuards=false, experimental rejoin off, then restart. |
| Guarded reliability baseline | Session Reliability on, diagnostics on, CommonDisconnectGuards=true, experimental rejoin off, then restart. |
| Two-client rejoin lab | Enable experimental rejoin on the host and non-host before room creation; keep diagnostics and guards on; test only with a recoverable non-host transport interruption. |
Built-in performance profiles
Selecting a profile writes the values shown below. It does not change Session Reliability, adaptive timing, overlay state, diagnostics retention or experimental-rejoin settings.
| Profile | FPS | VSync | MSAA | Shadows / distance | Pixel lights | Aniso | Texture limit | LOD | VFX | Particles/system | Trail multiplier |
|---|---|---|---|---|---|---|---|---|---|---|---|
Vanilla |
Startup value | Startup | Startup | Startup | Startup | Startup | Startup | Startup | Restored | Restored | Restored |
MaxFps |
-1 |
0 | 0 | Off / 0 | 0 | Off | 0 | 0.65 | Minimal | 128 | 0.35 |
Balanced |
120 | 0 | 2 | On / 35 | 2 | On | 0 | 1.00 | Reduced | 512 | 0.75 |
Competitive |
240 | 0 | 0 | Off / 10 | 0 | Off | 0 | 0.75 | Minimal | 192 | 0.45 |
Potato |
90 | 0 | 0 | Off / 0 | 0 | Off | 1 | 0.50 | Minimal | 96 | 0.25 |
Cinematic |
120 | 0 | 8 | On / 100 | 8 | On | 0 | 2.00 | Full | 10000 | 1.00 |
Custom |
Config | Config | Config | Config | Config | Config | Config | Config | Config | Config | Config |
-1 uses Unity's platform default rather than imposing a numerical cap. Vanilla restores the Unity values captured when the plugin started; it is not a hard-coded approximation of vanilla settings.
Changing one of the exposed graphics or VFX fields through the in-game menu selects Custom.
Adaptive T0-T3 budgets
Adaptive control operates only when:
- the performance module is enabled;
- the selected profile is not
Vanilla; - a metrics snapshot exists;
- Performance Improvements is not owning the overlapping VFX path.
The base particle cap and base trail multiplier come from the active profile/config. The effective particle limit is:
min(original maxParticles, configured ParticleLimitPerSystem, ceil(original maxParticles x tier retention))
An originally non-zero particle system always retains at least one particle slot.
| Tier | Particle retention | Emission retention | Trail lifetime | Expensive non-directional lights |
|---|---|---|---|---|
| T0 | 100% | 100% | base x 1.00 | Full: unlimited; Reduced: 24; Minimal: 12 |
| T1 | 80% | 90% | base x 0.85 | min(base, 20) |
| T2 | 60% | 72% | base x 0.65 | min(base, 10) |
| T3 | 40% | 55% | base x 0.45 | min(base, 5) |
Overflow lights remain visible but are switched to vertex rendering with shadows disabled. Directional lights are not tracked by the VFX controller.
Pressure and recovery use hysteresis:
- pressure: average FPS below 88% of target or 1% low below 68% of target;
- headroom: average FPS at least 97% of target and 1% low at least 84% of target;
- pressure delay: configurable, effective range 2-20 seconds;
- recovery delay: configurable, effective range 5-60 seconds;
- minimum hold after a tier change: 3.5 seconds;
- movement: one tier per qualified pressure/recovery interval.
If TargetFrameRate is below 30, adaptive control uses the display refresh rate, falling back to 60 FPS.
VFX discovery is periodic, not per frame. The effective rescan interval is 10-30 seconds. Tier changes immediately reuse already tracked objects and schedule discovery only when budgeting is active.
Session Reliability
Session Reliability is independent of the selected FPS profile. Disabling General.Enabled does not disable the passive recorder.
The recorder:
- samples lightweight connection state every second;
- refreshes heavier world counts every 10 seconds;
- keeps 30 recent runtime samples and 48 recent error fingerprints in bounded memory;
- refreshes the in-memory summary every two seconds;
- writes a snapshot on disconnect, room leave, shutdown and application quit;
- continues in memory if disk writing fails.
Diagnostics v2
Disk records use JSON Lines with schema:
reksik-session-reliability-v2
Each record can contain:
- UTC timestamp and capture reason;
- previous/current Photon client state and sanitized disconnect cause;
- connected, ready, in-room and master-client flags;
- Photon probe status and bounded probe issue identifiers;
- per-process anonymous room fingerprint;
- room player/max-player/open state when available;
- counts for players, Photon views, projectiles, gameplay entities, particle systems and trails;
- total exception/error counts and events in the last minute;
- sorted plugin GUID/version inventory;
- up to 30 recent connection/world-count samples;
- up to 48 recent error categories and salted fingerprints.
Default disk bounds are five files of at most 2 MiB each. Rotation and deletion of old diagnostic files are best-effort; a locked file never stops the live monitor.
Diagnostics deliberately exclude:
- player names;
- raw room names;
- IP addresses and endpoints;
- authentication values;
- raw exception messages;
- stack traces;
- filesystem paths.
Room and exception fingerprints use random per-process salts, so they are useful within one game launch but cannot reliably correlate identities across launches.
Fail-closed reliability guards
Session Reliability.CommonDisconnectGuards is enabled by default and requires a restart when changed. Every guard first verifies the expected plugin/assembly, type, method signature and required members. Missing or changed contracts are skipped; patch failure leaves the original method intact.
| Guard target | Narrow intervention |
|---|---|
EntityLagFixerUpper.RoundEnd |
Suppresses the audited unsafe local bullet-cleanup coroutine. |
NetworkPhysicsObject.OnCollisionEnter2D |
Skips only collisions missing required Photon/collision state. |
FollowPlayer.LateUpdate |
Stops updates whose followed player disappeared and can disable the stale component. |
SetPlayerSpriteLayer.Start |
Skips only the incomplete cosmetic hierarchy proven to abort RWF player-leave cleanup. |
RWF.UI.FollowPlayer.Update |
Disables an orphaned RWF spotlight before it dereferences a departed player. |
ModdingUtils PlayerPatchFullReset.Prefix |
Skips effect cleanup only for an already-destroyed Player; live players keep full cleanup. |
ProjectileHit.RPCA_DoHit |
Installs only after proving both exact target lookups and terminal vanilla DestroyMe. Valid resolved targets and the audited targetless sentinel pair stay vanilla; a missing, invalid or throwing runtime target is cleaned up through DestroyMe best-effort before the unsafe RPC is suppressed. |
GearUp OrbLifeforceDualityModifier.Update |
Makes the audited terminal orb-destroy path owner-only and one-shot. |
GearUp ArcaneConversionModifier.DoHitEffect |
Ignores only the audited hit with no target transform. |
The menu reports guard active/off state, patched count and intervention count. Skipped/failed patch counts remain available through startup logs and the status API. These guards target confirmed error signatures; they are not a general promise to make every mod combination safe.
Experimental non-host room-bound hot rejoin
Safety status
Experimental rejoin is OFF by default and all of its config fields are read at startup. Restart after changing them.
It is designed for a short recoverable transport interruption affecting a non-host. It is not host migration and it is not a save-game system.
Eligibility contract
A new attempt starts only when all of these client-side conditions are true:
- the reconnecting client enabled the feature before startup;
- the client was previously observed in that room as a non-host;
- exact Photon, ROUNDS and UnboundLib critical hooks were resolved;
- a locally owned player produced a room-bound warm-state receipt no more than one second before the disconnect;
- the receipt has a valid room name and Photon actor number;
- the disconnect cause is exactly
Exception,ServerTimeoutorClientTimeout.
For actor retention to work, the host must also have created the room with the experiment enabled. There is currently no peer config handshake and no per-attempt proof that the room has the expected TTLs; mismatched setup therefore reaches Photon and normally fails or falls back within the bounded attempt.
Kicks, authentication failures, explicit client disconnects, operation limits, unknown causes and missing/stale room receipts keep the vanilla disconnect path.
What happens
- Room creation patches set
PlayerTtlandEmptyRoomTtlon a non-null PhotonRoomOptions. - While Photon marks an actor
IsInactive, the audited ROUNDS, UnboundLib and optional RWF player-left callbacks are temporarily suppressed. - The client calls
ReconnectAndRejoin()once and waits up toReconnectTimeoutSeconds. - If needed, it resets the transport, waits up to three seconds for disconnect, waits
RetryDelaySeconds, then performs one final attempt. - A successful transport attempt must return to an in-room, connected-and-ready state.
- The result remains
RejoinedWarmStatePendinguntil the nextPointStart. - At
PointStart, the controller waits up to eight frames for the locally owned player and validates the frozen receipt.
The point-boundary validation requires:
- the same Photon room;
- the same ROUNDS player ID;
- the same Photon actor number;
- a locally owned reconstructed player;
- finite, positive health and max-health values;
- an exact multiset match of internal/display card identities and duplicate counts;
- a frozen receipt no older than 300 seconds.
On a successful validation, ammo is reloaded when the pre-disconnect player had a GunAmmo state and the warm-state warning is acknowledged. The normal point lifecycle may recreate some transient card state, but this controller neither verifies nor guarantees that.
Health is validated but not overwritten from the snapshot.
Exact guarantees
When the feature reports a completed point-boundary restore, it has verified same-room ownership, player ID, actor number and exact deck composition, and it has normalized ammo at a new point. It has not reconstructed the interrupted point.
There are at most two transport attempts: one initial attempt and one retry. Each attempt has a bounded timeout. Exhaustion calls the audited vanilla NetworkConnectionHandler.NetworkRestart() fallback.
Limitations
- The host is never auto-rejoined.
EmptyRoomTtldoes not provide host migration or elect a new master client.- A host enabling the feature only prepares rooms; it does not make the host recoverable.
- A client enabling it after room creation cannot add TTL retention retroactively.
- Health, coroutine progress, temporary card counters, arbitrary card-owned MonoBehaviours, spawned objects, projectiles and mid-point world state are not restored.
- Exact deck mismatch, actor mismatch, room mismatch, expired receipt, invalid health or missing owned player rejects warm-state acknowledgement.
- A rejected point-boundary validation never fabricates old state and does not call
NetworkRestart(). At that safe boundary it clears the pending receipt so fresh stable-state receipts can arm future disconnect attempts. - The allowed timeout/TTL ranges are independent. A very short
PlayerTtlcan be incompatible with a long two-attempt envelope. - Only the first locally owned ROUNDS player found by the controller is covered; couch/multiple-local-player restoration is not guaranteed.
- Third-party mods may destroy player state through callbacks outside the audited baseline.
- Harmony patches with unknown IDs/order and optional callback drift outside the mandatory baseline are not covered.
- RWF integration is optional and signature-checked; the base ROUNDS and UnboundLib inactive-player hooks remain mandatory.
- If a required symbol or critical hook is missing, state becomes
FailedClosedand vanilla disconnect handling remains active.
Compatibility
Performance Improvements
When pykess.rounds.plugins.performanceimprovements is detected:
Detection uses that exact GUID. A renamed fork with another GUID is not automatically recognized.
- ReksikPerformance disables its overlapping VFX controller and adaptive VFX tiers;
- shadow, shadow-distance and pixel-light controls are deferred;
- frame cap, VSync, MSAA, texture limit, LOD bias and anisotropic filtering can still apply;
- Session Reliability, diagnostics and compatible reliability guards remain available.
This prevents two mods from repeatedly fighting over the same visual resources.
Other mods
- Reliability guards are version/signature aware and skip unknown contracts.
- Optional Rounds With Friends inactive-player handling is used only when its exact callback exists.
- Networking guards do not guess authority. Unclear ownership keeps the original/vanilla path.
- VFX controls restore the values captured before ReksikPerformance touched each tracked object.
Complete config reference
Config file: BepInEx/config/com.reksikperformance.rounds.cfg
Ranges below are effective runtime ranges for finite values. Out-of-range values are normalized when applied; use finite numbers in manually edited config files.
| Section | Key | Type | Default | Effective values/range | Meaning |
|---|---|---|---|---|---|
| General | Enabled |
bool | true |
true/false |
Apply the selected local performance profile. Does not disable Session Reliability. |
| General | Profile |
enum | Balanced |
Vanilla, MaxFps, Balanced, Competitive, Potato, Cinematic, Custom |
Select a built-in profile or use config values. |
| Session Reliability | Enabled |
bool | true |
true/false |
Run passive session monitoring independently of the FPS profile. |
| Session Reliability | WriteDiagnosticsToDisk |
bool | true |
true/false |
Write bounded diagnostics v2 JSONL files. |
| Session Reliability | RunInBackground |
bool | true |
true/false |
Keep this Unity client active while unfocused to reduce Alt-Tab stalls. |
| Session Reliability | CommonDisconnectGuards |
bool | true |
true/false |
Install exact-signature reliability guards. Restart required. |
| Experimental Rejoin | Enabled |
bool | false |
true/false |
Enable the non-host room-bound experiment. Host and client opt-in plus restart required. |
| Experimental Rejoin | PlayerTtlMilliseconds |
int | 60000 |
5000-300000 | Time Photon retains an inactive actor slot. Restart required. |
| Experimental Rejoin | EmptyRoomTtlMilliseconds |
int | 30000 |
0-300000 | Time Photon retains an empty room allocation. Not host migration. Restart required. |
| Experimental Rejoin | ReconnectTimeoutSeconds |
float | 12 |
3-30 | Timeout for each of the two transport attempts. Restart required. |
| Experimental Rejoin | RetryDelaySeconds |
float | 1 |
0.25-5 | Delay before the single retry. Restart required. |
| Diagnostics | ShowOverlay |
bool | false |
true/false |
Show the local metrics overlay. |
| Frame Pacing | TargetFrameRate |
int | 120 |
-1, or 30-1000 |
Unity target FPS; -1 uses the platform default. |
| Frame Pacing | VSyncCount |
int | 0 |
0-4 | Unity VSync interval. |
| Adaptive | Enabled |
bool | true |
true/false |
Enable adaptive VFX pressure control. |
| Adaptive | PressureSeconds |
float | 4 |
2-20 | Sustained pressure required before moving up one tier. |
| Adaptive | RecoverySeconds |
float | 10 |
5-60 | Sustained headroom required before recovering one tier. |
| Graphics | AntiAliasing |
int | 2 |
0, 2, 4, 8 | Local MSAA samples; other values normalize downward to a supported step. |
| Graphics | Shadows |
bool | true |
true/false |
Enable local real-time shadows unless compatibility mode defers them. |
| Graphics | ShadowDistance |
float | 35 |
0-500 | Local shadow draw distance unless compatibility mode defers it. |
| Graphics | PixelLightCount |
int | 2 |
0-32 | Unity pixel-light count unless compatibility mode defers it. |
| Graphics | AnisotropicFiltering |
bool | true |
true/false |
Force-enable or disable anisotropic filtering. |
| Graphics | TextureLimit |
int | 0 |
0-3 | Unity master texture downscale level; 0 is full resolution. |
| Graphics | LodBias |
float | 1 |
0.1-5 | Local level-of-detail bias. |
| VFX | Budget |
enum | Reduced |
Full, Reduced, Minimal |
Select the static VFX/light budget. |
| VFX | ParticleLimitPerSystem |
int | 512 |
16-10000 | Base maximum particles for each tracked system. |
| VFX | TrailLifetimeMultiplier |
float | 0.75 |
0-1.5 | Base multiplier applied to tracked trail lifetimes. |
| VFX | RescanSeconds |
float | 12 |
10-30 | Delay between active VFX discovery passes. |
Paths
| Purpose | Path |
|---|---|
| Config | BepInEx/config/com.reksikperformance.rounds.cfg |
| Diagnostics v2 | BepInEx/config/ReksikPerformance/diagnostics/session-reliability-*.jsonl |
| Main BepInEx log | BepInEx/LogOutput.log |
| In-game controls | UnboundLib mod settings -> REKSIK PERFORMANCE |
When reporting a disconnect, include the matching diagnostics JSONL and BepInEx log. Review files before sharing them even though the writer intentionally excludes direct personal/network identifiers.
Testing checklist
Performance
- Enable the overlay and note FPS, 1% low and frame time at T0.
- Create a repeatable VFX-heavy round.
- Confirm pressure persists for the configured delay before the tier rises.
- Confirm the tier holds for at least 3.5 seconds.
- Remove pressure and confirm recovery happens one tier at a time.
- Select
Vanillaor disable the performance module and confirm captured graphics/VFX values are restored.
Diagnostics and guards
- Start a room and confirm the menu shows a Session Reliability sample.
- Check active/patched/intervention counts in the menu and skipped/failed details in
LogOutput.log. - Produce a controlled room leave or client disconnect.
- Confirm a v2 JSONL record appears in the diagnostics directory.
- Confirm the file contains no raw room/player names, address, exception message or stack trace.
Experimental rejoin
Use two machines or two isolated clients.
- Enable the experiment on host and non-host; restart both.
- Let the opted-in host create a new room.
- Confirm the non-host has a fresh
SnapshotReadywarm-state receipt. - Interrupt only the non-host transport briefly.
- Confirm the actor becomes inactive rather than being destructively removed.
- Confirm
ReconnectAndRejoinreturns the same actor to the same room. - Do not treat mid-point transport success as gameplay recovery.
- Continue to the next point and confirm same-room/player/actor/deck validation succeeds.
- Repeat with a deck mismatch or expired receipt and confirm restoration is rejected.
- Test attempt exhaustion and confirm vanilla
NetworkRestart()fallback. - Test an unconfigured third peer in a mixed lobby and confirm the limitation is understood.
- Test a room-creation path with null/unprepared
RoomOptions. - Test an optional RWF callback mismatch without confusing it with mandatory-hook
FailedClosed. - Test a deliberately incompatible short TTL / long retry envelope.
- After a rejected warm validation, confirm no old state is fabricated, no automatic restart occurs, and a later healthy point can produce a fresh
SnapshotReadyreceipt.
FAQ
Does every player need the same FPS profile?
No. Performance profiles are local rendering choices.
Why did VFX or shadow settings do nothing?
Performance Improvements may be installed. In compatibility mode, ReksikPerformance deliberately defers overlapping VFX, shadow and pixel-light ownership.
Why did FPS not improve?
The active bottleneck may be gameplay scripts, physics, networking, the driver or a cap outside Unity. Use the overlay and compare a repeatable scene rather than assuming every low-FPS case is VFX-bound.
Can the host hot-rejoin?
No. The rejoin subsystem rejects previously observed host context. EmptyRoomTtl is room retention, not host migration.
Does successful transport rejoin mean I can continue the interrupted point?
No. Wait for the next point-boundary validation. Transport success alone leaves NeedsWarmStateRestore=true.
Does point-boundary restore copy my old health?
No. It verifies that reconstructed health is finite and positive, but never overwrites it from the receipt. It verifies exact deck composition and reloads ammo when applicable.
Why did rejoin use vanilla behavior instead?
The BepInEx log now records a parseable [warm-receipt=...] reason such as host, no-room, no-owned-player or stale-receipt. Other likely reasons include a non-recoverable cause, an unprepared/expired actor slot, or missing/changed critical runtime hooks. Missing critical contracts fail closed; host/TTL/setup mismatches use the bounded Photon attempt or vanilla fallback described above.
Are diagnostics safe to upload publicly?
They are privacy-minimized, not magically risk-free. Direct player/room names, IPs, auth data, raw messages and stacks are excluded. Plugin GUID/version inventory and anonymous per-launch fingerprints remain, so review files before sharing.
Rejoin/warm-state transitions are not embedded in diagnostics v2 JSONL. Use the live menu and BepInEx/LogOutput.log for that state machine.
Which settings require a restart?
CommonDisconnectGuards and all Experimental Rejoin fields are startup-installed behavior. Restart after changing them.
Dependencies
BepInExPack_ROUNDSUnboundLib
