SleepeyDev-OnTogetherFrameCare icon

OnTogetherFrameCare

More frame rate with the same picture: outlines drawn in a pass of their own in dense views, less main-thread work on busy servers, a 600 fps cap. F2 saves a performance report; Shift+F2 tests what would help.

Last updated 3 days ago
Total downloads 19
Total rating 1 
Categories Mods
Dependency string SleepeyDev-OnTogetherFrameCare-1.0.0
Dependants 0 other packages depend on this package

This mod requires the following mods to function

BepInEx-BepInExPack-5.4.2305 icon
BepInEx-BepInExPack

BepInEx pack for Mono Unity games. Preconfigured and ready to use.

Preferred version: 5.4.2305

README

On-Together: FrameCare

More frame rate where the island gets dense, with the same picture. And when you want to know where your frame rate goes: press F2 for a report, Shift+F2 to find out what would help, Ctrl+Shift+F2 to find out what the other players cost.

Quick start

Install with the Thunderstore Mod Manager or r2modman; BepInExPack comes with it. To install by hand, put OnTogetherFrameCare.dll in BepInEx/plugins/SleepeyDev-OnTogetherFrameCare/. The boosts are on from the first launch; nothing else is needed.

Keys. None of them does anything while you are typing.

Key What it does
F2 Saves a performance report
Shift+F2 Starts or cancels an experiment run (about 70 s; keep the mouse and keyboard still)
Ctrl+Shift+F2 Starts or cancels a players run (about 72 s; keep the mouse and keyboard still)
Ctrl+F2 Switches the outline pass for this game session, to compare the picture

Everything FrameCare changes happens on your computer only: other players see nothing different, and nothing about the network changes.

Settings are in BepInEx/config/com.sleepeydev.ontogether.framecare.cfg, created on first launch (see Configuration below).

Files. FrameCare writes to BepInEx/FrameCare: a report in reports each time you press F2, Shift+F2 or Ctrl+Shift+F2 (about a quarter of a megabyte each, never deleted by FrameCare), a session log and a hitch log per game launch in sessions (the newest 20 launches are kept), and two small lists of what the engine offers, available-playerloop.txt and available-counters.txt, overwritten when FrameCare starts its timing or saves the first report of a launch.

Only want the boosts? Set Diagnostics / MainThreadProbes = false and SessionLog / Enabled = false. FrameCare then writes no session or hitch log; F2 still takes reports.

Uninstall by removing it in the mod manager, then delete BepInEx/FrameCare and the .cfg file if you like. FrameCare never saves anything to the game's own settings: what it changes lasts only while the game runs.

The boost

The game's Toon shader draws most of the island's objects twice: once for their colour and once for their black outline. The engine draws those two passes object by object, so it switches shaders for almost every draw and can batch almost nothing. On the test machine, in dense views, that switching, not the graphics card, was what held the frame rate down.

FrameCare draws all the colour passes first and all the outline passes straight after, in a pass of its own. Every outline is still drawn, and the picture looks the same.

Measured result. On the test machine, at the edge of the map looking over the island, the frame rate went from about 64 fps to about 244 fps: about 4x the frame rate in dense views, same picture. Draw calls and triangles stayed the same (4,737 and 4,734 draw calls, 948 K triangles both times); only the shader switches fell, from about 4,000 to about 340. Elsewhere on the island it ran at 273 fps. How much you gain depends on your machine and on what you are looking at: an open view with little on screen has less to gain, and a machine held back by its graphics card gains less.

It is on by default. To turn it off, set Boost / OutlinePass to false in the config (see Configuration below); the change applies straight away. To compare the picture, press Ctrl+F2 (see below).

What can look different. The moved passes now come after all solid objects instead of in the engine's usual order. For an ordinary solid surface that makes no difference. It can for a material that only writes depth or stencil (a mask), one that blends while sitting among the solid objects, and two surfaces lying exactly on top of each other. The same move also takes the few solid objects whose only colour pass is an unlit one (some flowers and props) into that second pass: they are still drawn, with depth, just a moment later. See-through objects (water, glass, particles, text, UI) are not affected. An F2 report lists the materials where the order can matter, under the Shaders table.

When it is not available. If URP in your game is set up differently from what FrameCare checked (deferred rendering, a feature that needs rendering layers, render graph off), the boost changes nothing, logs why once, and the F2 report shows the reason. Pressing Apply in the game's Graphics settings rebuilds the renderers; FrameCare moves the outline pass to the new ones by itself, and if the pass was not available before, it checks again with the new settings.

Frame-rate cap

Boost / FrameRateCap, default 600. When nothing holds the game back it can run at several hundred frames per second, more than almost any monitor shows, and the computer works harder and warmer for it. The cap stops it at 600. It only applies while vSync is off (with vSync on, your display already sets the frame rate), and if you chose a lower limit in the game's own settings, that limit stays. Set it to 0 for no cap; values from 1 to 29 count as 30. FrameCare puts the game's own limit back when you set it to 0, while an experiment runs, and when the game closes.

Boosts for busy servers

On a server with many players the main thread, not the graphics card, holds the frame rate back, and a large part of its time goes to the other players' avatars and to work the game repeats every frame. Five settings in the Boost section save some of it without changing anything you see. All five are on by default, and all of them work only on your computer: other players see nothing change, and nothing about the network changes. Your own avatar is never touched.

  • RemoteControllerToggleOff: the network code stops switching each other player's CharacterController off and on around every move. Nothing looks different. One small physics difference: when you walk into a moving player, their collision capsule catches up with their avatar at the next physics step (at most 0.02 s, a few centimetres while walking) instead of every frame.
  • HiddenTailSoftBoneOff: a tail's soft-bone physics pauses while that player wears no tail (the game keeps it running for the hidden tail) and comes back as soon as a tail is shown.
  • RemoteGroundCheckKinematic: other players' ground-check physics bodies stop colliding with the ground. The game only reads your own.
  • MenuLookupCache: on the island the game's sound manager searches the whole scene for the main menu every frame. FrameCare answers from the game's own cache and searches for real after every scene load, whenever the island is not loaded, and at least once a second, so the answer is always the game's.
  • AmbientSoundLookupCache: on the island the game places the shore and river sounds at the nearest point of two curves every frame. FrameCare reuses the last position while you move less than 2 cm, for at most 0.1 s, and looks it up for real otherwise, after every scene load and whenever a curve changes. A reused position is at most 0.1 s old, and its distance from you is never off by more than 2 cm.

Measured result. Two players runs, each on the test machine described below. The first, on a 13-player server (12 other players' avatars around) and steady from start to end, came from a test build without the ambient sound cache: the menu lookup cache alone saved about 1.15 ms of main thread per frame (177 to 222 fps), and the four boosts of that build together, the menu lookup cache included, about 1.35 ms (232 fps). In the second, on a 23-player server, the main thread took 5.84 ms per frame (171 fps) with the boosts paused; the controller toggle saved about 0.18 ms, the tail about 0.15 ms and the ground check about 0.08 ms (no bigger than the run's drift). A 24th player joined before the menu cache step, so the report marks the later steps as not trustworthy. They still read about 1.14 ms for the menu lookup cache, about 0.07 ms for the ambient sound cache and about 1.60 ms (235 fps) for all five together. How much you gain depends on your machine and on how many players are around; alone on the island there is less to gain. FrameCare puts back every value it changed when you switch a setting off, change scene, quit, or start an experiment run, and leaves a value alone if something else changed it. The F2 report's boost section shows what each of them holds right now.

Two optional settings

Both are off by default, because each one changes what you see. Turn them on in the config if you want more frame rate and accept the trade-off.

Lower LOD bias (Boost / LodBiasFactor, default 1.0). The game's LOD bias is multiplied by this. Below 1.0, buildings, props and trees switch to their simpler models closer to you. Measured without the outline pass, 0.5 gained about 13 fps in the test view (64 to 76 fps). With the outline pass on, as it is by default, this setting and the next one together took the same view from about 244 to about 290 fps: far less time saved per frame, so on a machine running at 60 fps expect a few fps, not tens. The cost: simpler-looking models at middle distance, and the switch between detail levels is easier to notice as you move. FrameCare applies the factor right after the game sets its own LOD bias (at startup and when you press Apply in Graphics settings), never multiplies it twice, and puts the game's value back when you set the factor to 1.0 again.

Hide small objects far away (Boost / HideSmallFarObjects, default false). Small scenery objects (bounding radius under SmallRadius, 0.75 m) are not drawn when they are farther than FarDistance (35 m) from the camera. Measured without the outline pass, this gained about 38 fps in the test view (64 to 101 fps); with the outline pass on, see the combined figure above. The cost: small props, flowers and grass far away disappear, and they appear again as you come closer (they are shown again once closer than FarDistance and hidden 2 m past it, so they do not flicker at the edge). Your avatar, other players, your desk, customization objects, highlighted objects, 3D text and objects moved by physics (such as the basketball) are never hidden. In desk, swan and overlay views the distance is measured from the camera showing the view. FrameCare only ever shows again what it hid itself, and shows everything it hid when you turn the setting off.

How the numbers were measured

Every figure on this page comes from one test machine: a desktop with an RTX 5080 graphics card and a Ryzen 9 9950X3D processor, at 3440x1440 with the game's High quality on Direct3D 11, without Day and Night or any other mod that changes rendering. The outline pass and the two optional settings were measured in one experiment run, standing still at the edge of the map looking over the island. The busy-server boosts were measured in two players runs, with 12 other players (a steady run) and with 22 to 23 (a player joined during the run). On a 49-player server the outline pass took the same machine from 31 to about 110 fps. Nothing is averaged over several machines, so read the figures as an example, not a promise: another processor, graphics card, resolution or view gives other numbers.

Diagnostics

The island runs fine facing the road and noticeably worse facing the canteen. FrameCare measures why, so that decisions about shadows, outlines or mods can be made from numbers rather than guesses. Reports only read. Experiment runs change one thing at a time for a few seconds and put it back straight away.

F2: taking reports

  1. Face whatever you want measured and stand still for about five seconds.
  2. Press F2. A notice at the top of the screen says report #3 saved.
  3. Write down what that number was — "3: facing the canteen from spawn", "4: same, Day and Night off" — and compare.

The key does nothing while you are typing in chat, a sticky note or any other text field.

Reports go to BepInEx/FrameCare/reports, named like 0003_2026-09-13_16-40-12.txt, with a .json next to each holding the same data for scripts. Numbers carry on across game launches: the next report takes the highest number in the folder plus one, so a number is not reused as long as a newer report stays. Each report is about a quarter of a megabyte; delete old ones whenever you like, FrameCare never does. In the Thunderstore Mod Manager, Settings → Browse profile folder opens the folder that contains BepInEx.

What a report contains

  • Frame rate over the last 5 and 30 seconds: average, 1% low, worst frame and frame-time percentiles, plus how long the render pipeline spends on the main thread. The frame in which the report itself is taken is left out, so taking one never skews it.
  • Where the frame goes: the engine's own timers — main and render thread, the time the main thread waits for the render thread, and the time spent waiting on the GPU — with a one-line reading such as "the main thread waits for the render thread, so submitting draw calls is the limit". A timer the game does not record shows n/a, never 0. There is no split by render pass (opaque, shadows, depth): the released game does not record those timers. An experiment run shows what shadows cost instead.
  • CPU threads: for about two seconds after the key press, how busy each of the game's threads is (as a percentage of one core), the same grouped by name (all "Job.Worker" threads together, for example), how many cores the whole game used, how many worker threads Unity runs, and a short reading. On a busy server the game's main thread is usually what limits the frame rate (the "where the frame goes" reading says which): the engine only hands the worker threads work it can split up (animation, culling, skinning), while the game's own scripts and moving things around stay on the main thread. So idle cores do not by themselves mean the game is leaving speed unused. Windows only roughly times threads that run in short bursts, so treat differences of a few percent as noise. The report is saved once the two seconds are over; its frame-rate figures are still the ones from the moment you pressed F2, and an experiment run cannot start until then. Windows only.
  • Main thread breakdown: where the main thread's time went over the last 5 seconds, by part of the engine's frame: scripts (Update, LateUpdate, FixedUpdate), physics, animation, particles, canvases and UI, renderers and skinning, rendering, waiting for and handing over the present, input, audio and the rest. For each: average, share of the main thread, 95th percentile and worst frame, then the ten busiest single steps. FrameCare puts a timestamp between every step of the engine's frame, so these are parts of the frame, not what the engine does inside one step, and they include the timestamps' own small cost, which the report measures and states (an average in ns per timestamp). The report also says how much of the engine's own main thread time all the rows cover, waiting included, and warns when that is below 90%. The waiting part is not all idle time: it holds the present calls too and can include the render thread catching up on the previous frame. A second table splits the same parts into frames in which the mouse moved and frames in which it did not (n/a with fewer than 30 frames of a kind), read from the Input System's mouse, which also counts movement while the right mouse button turns the camera. It shows whether frames with mouse movement took longer, not why: moving the mouse often comes with other things. Take the report with the mouse moving during the 5 seconds before the key, not after it. It also says how many input events per second the game processed: over all the frames the mouse's figure is a lower bound on your mouse's polling rate, never the rate itself, since the game merges a mouse's moves before they can be counted, and while the mouse moves there is at most about one per frame. Frames while the game window has no focus are in neither group. Two more rows: Between phases, the time between two parts of the frame that no row covers, and Between frames, the time from the end of one frame to the start of the next (window messages and engine bookkeeping), with the loop period next to them.
  • Scripts by type: during the CPU threads window, every script that is running (the game's, PurrNet's, Unity's and every mod's, FrameCare's own included): the 30 that take the most time per frame, with where each comes from, calls per frame and time per call, the timing's own overhead measured and taken off; totals for the game, PurrNet, Unity and each mod; and how much of the main thread's script time no timed script accounts for. Adding hundreds of timers takes a moment, so it is spread over frames and the report says how long it took; the report can take a few seconds more to save. The timers are removed again before the report is saved.
  • Scene searches: over the same frames, every search of the scene with Unity's usual methods: what was searched for, which script asked (a search that goes through one of FrameCare's caches is named with it, for example "SoundManager.Update (through FrameCare's menu lookup cache)"), how often per frame and how long it took.
  • Graphics settings in force right now: quality level, LOD bias, shadow distance and cascades, shadow map size, MSAA, render path, lights. These are read live, after the game and any mods have applied their changes. The opaque pass line shows depth priming, which shader passes the opaque pass draws, and whether FrameCare's outline pass is on.
  • FrameCare boosts: whether the outline pass is on, off, switched for this session with Ctrl+F2 or not available (and why); the LOD bias factor with the game's own LOD bias and the value in force; how many small far objects are hidden right now, out of how many could be; the frame-rate cap with the game's own limit, vSync and the value in force; and what each busy-server boost holds right now.
  • Engine counters: draw calls, batches, SetPass calls, triangles, shadow casters, memory — averaged over the same 5 seconds.
  • What is being drawn, estimated per object and per group: renderers on screen, triangles, camera and shadow draws. The game draws much of its grass, bushes and books through GPU instancing instead of ordinary renderers; those are listed in their own table.
  • Shaders: which shaders the scene uses, how many materials and draws each has, every pass of the shader, and how many of its materials leave the shadow and outline passes on ("no shadow pass" for shaders that cannot cast shadows at all). Below it, the materials whose look can depend on draw order once the outline pass moves their unlit or outline pass.
  • Characters: player avatars, skinned meshes, and animators — including the ones animating while nothing of theirs is on screen — and a census of the other players' avatars: culling modes, soft bones simulating without a tail, ground-check bodies, renderers casting shadows, playing particles and transforms per avatar.
  • Particles, lights and world-space UI, and the list of loaded mods with versions.
  • Hitches in this session: how many frames were slower than 50 ms so far (counting the time between frames before them), the ten slowest with what they spent their time on, including between frames or between phases, how much of it the main thread spent on the CPU, and what happened just before, and counts by part of the frame and by event.
  • Garbage collection (under "where the frame goes"): collections in the last 5 seconds, the Mono heap and the collector's mode.

The draw estimates are upper bounds, printed next to the engine's own counters so you can see how far apart they are. The explanation of every column is at the end of each report.

Shift+F2: experiment runs

An experiment run tries the changes a report points at, one at a time, and measures each one. It answers questions like "would hiding far grass help, or turning outlines off?" with numbers. Before the first step it pauses every boost that is on, so the baselines measure the game's own rendering, and it turns them back on when the run ends or stops. The report says which boosts were paused.

How to run one

  1. Turn vSync off and raise or remove the frame-rate limit in the game's settings, then close the settings window. A capped frame rate hides every gain; the report warns if it is capped.
  2. Stand somewhere with a slow view: the canteen, or the edge of the map looking over the island.
  3. Press Shift+F2 (either Shift key works).
  4. Do not touch the mouse or keyboard for about 70 seconds (thirteen steps of about 5.5 seconds each). The camera must stay still. If it moves more than a quarter of a metre or turns more than 2°, the run stops.
  5. When the line at the top of the screen goes away, the report is saved.

What the steps do

Each step changes one thing, waits 1.5 seconds, measures for 3 seconds, puts the change back, and waits 1 second before the next step.

# Step What it tries
1 Baseline (start) Nothing changed, to measure against
2 GPU check Half resolution, no MSAA: does the graphics card matter at all?
3 All shadows off What all shadows cost together
4 Small objects' shadows off Objects under 0.75 m cast no shadow
5 LOD bias lower Buildings, props and trees switch to simpler models sooner
6 Small objects far away hidden Small things more than 35 m away are not drawn
7 Grass hidden Just the grass, to compare with 6
8 Outline off far away The black outline is not drawn beyond 35 m
9 Outline off everywhere The Toon outline is not drawn, except on materials that avatars or the game's instanced fences, decks, lamps and trunks also use
10 Combined levers 4, 5, 6 and 8 together
11 Outline in its own pass The outline pass boost: every outline kept, drawn in a separate pass so the game can batch them
12 Combined: outline pass + LOD + far small hidden 11, 5 and 6 together: all three boosts
13 Baseline (end) Nothing changed, to see how much the frame rate drifted

Steps 2 and 3 are diagnostics, not settings to keep. You will see the grass, outlines and shadows disappear and come back during the run; that is expected.

Reading step 11's numbers. Every outline is still drawn, so draw calls and triangles should stay at the baselines; only SetPass calls and render time should fall. If draw calls or triangles drop, the outlines were not drawn and the gain is not real: the report then marks step 11 with *.

Everything is put back. Every change is undone when its step ends, and also when you cancel, change scene or quit the game. Nothing is saved to the game's settings or to FrameCare's config; the only thing written is the report. If something else changes a value while a step has it (for example you press Apply in the game's Graphics settings), FrameCare leaves the game's value in place and the report says so.

Cancelling. Press Shift+F2 again. The run also stops by itself if the camera moves, the scene changes, you sit at your desk or start drawing. Everything is put back and a partial report says why. F2 takes no report while a run is going.

The report is saved as NNNN_<date>_experiment.txt (and .json) in BepInEx/FrameCare/reports, with the same numbering as F2 reports. Its table has, per step: how many objects were changed, average fps, the difference from the baselines in fps and %, 95th-percentile frame time, draw calls, SetPass calls, shadow casters, main and render thread time, and CPU cores (how much CPU the whole game used while the step was measured). A second table, "Main thread by phase per step", splits each step's main thread time into the same parts as the F2 report's main thread breakdown.

  • A step is marked * (not trustworthy) when something changed under it: someone joined or left, the settings window was opened, or the game's settings reset a value the step had set. Run it again.
  • A difference marked ? is no bigger than the drift between the two baselines, so it may be noise. One marked ~ was measured against a baseline that was itself marked *.
  • Your avatar, other players, the customization and desk objects, particles and UI are never touched. The game's GPU-instanced bushes, trunks and grass bases are not ordinary objects, so only step 3 affects them.
  • The report warns if vSync or a frame-rate limit caps the result, and if a mod that changes shadows or the camera, such as Day and Night, is loaded.

Ctrl+Shift+F2: players runs

On a server with many players the frame rate is held back by the main thread, and a large part of its time goes to the other players' avatars. A players run measures what they cost and which parts of it could be saved. It only measures: nothing is kept. The busy-server boosts came out of earlier players runs, and steps 3-7 measure each of them on its own.

How to run one

  1. Join a server with other players in view. Without another player's avatar in the scene the run does not start.
  2. Turn vSync off in the game's settings. Leave FrameCare's outline pass on (the default): without it the graphics side is usually the limit and main-thread savings do not show. If you use BlueSage QoL Tweaks, turn its Player Render Saver off: the run does not start while it is on, and stops if it is switched on.
  3. Press Ctrl+Shift+F2 (either Ctrl and either Shift key; Alt+F2 is the NVIDIA overlay's Photo Mode) and do not touch the mouse or keyboard for about 72 seconds (thirteen steps). The same camera rule as Shift+F2 applies.
# Step What it tries
1 Baseline (start) Nothing changed, to measure against
2 Animation off (diagnostic) Other players stand frozen: what their animation costs in total
3 CharacterController not toggled The RemoteControllerToggleOff boost alone (should look the same)
4 Ground-check bodies kinematic The RemoteGroundCheckKinematic boost alone (should look the same)
5 Tail soft bone off without a tail The HiddenTailSoftBoneOff boost alone (should look the same)
6 Menu lookup cached The MenuLookupCache boost alone (should look the same)
7 Ambient sound lookups cached The AmbientSoundLookupCache boost alone (should look and sound the same)
8 Idle effect renderers off (diagnostic) Particle effects that are not playing are not drawn; one that starts during this step is not shown until it ends
9 Avatar shadows off (diagnostic) Other players cast no shadows: what their shadows cost in total
10 Far avatars cast no shadows Players farther than 20 m cast no shadows (you can see this)
11 Far avatars hidden and paused Players farther than 30 m are not drawn or animated
12 Combined, no visual change 3, 4, 5, 6 and 7 together
13 Baseline (end) Nothing changed, to see how much the numbers drifted

Reading it. The column that matters is Δ main ms: how much time the main thread saved (negative) or lost against the baselines. Players move around and change what they are doing, so the numbers drift by themselves; a difference marked ? is no bigger than that drift. Steps 2, 8 and 9 are diagnostics, not settings to keep, steps 10 and 11 change what you see (a player who comes closer during them keeps the change until the step ends), and the others are meant to look identical; if something looked different during one of them, note the step. Your own avatar and everything that is not another player's avatar are never touched. The outline pass, LOD bias and hiding small far objects stay as they are set (hiding small far objects stops looking for new objects until the run ends). The frame-rate cap and the five busy-server boosts are paused, so the baselines show the game's own behaviour and steps 3-7 show what each boost saves; the report says what was paused.

The phase table shows each step's main thread parts against the baselines too, so you can see which part a step changed; a step whose frame rate moved while the part it works on did not is marked !. A step marked (partial) had FrameCare's timestamps for only part of its measuring (they were put back or switched off meanwhile); its parts are shown but not compared.

A step that would animate far players only every fourth frame is not in the run yet: the way Unity offers to do it needs checking in the game first, or its numbers could show a gain that is not real.

The report is saved as NNNN_<date>_players.txt (and .json) next to the other reports. Ctrl+Shift+F2 again cancels, and the run stops and puts everything back on its own for the same reasons as Shift+F2.

Ctrl+F2: comparing the picture

Press Ctrl+F2 to switch the outline pass off (Outline pass: game default (this session)) and again to switch it back on (Outline pass: FrameCare (batched) (this session)). Look at the same view both ways: outlines close by and far away, the edges of water, unlit props and flowers, particles, text and the UI. An F2 report taken each way shows the difference in SetPass calls.

The switch lasts for the rest of this game session. It never changes the config: the next time you start the game, the config decides again, and changing OutlinePass in the config also replaces it. It is not available during an experiment run.

The session log

While you play, FrameCare writes one line per second to BepInEx/FrameCare/sessions: frame rate, frame-time percentiles, where the camera is and which way it faces, the scene, the number of players, and the report number on the line where you pressed F2. During an experiment run the lines are tagged like exp0006:s3. One file per game launch. Open it in any spreadsheet. Each line also has the main thread time, its main parts (Update, LateUpdate, FixedUpdate with physics, animation, rendering, the rest), garbage collections in that second and the Mono heap; mouse_moving_share is the share of that second's frames in which the mouse moved; the last columns are between_ms and gaps_ms (between frames and between phases) and input_events_per_s and mouse_events_per_s (input events the game processed, a lower bound).

Only the newest 20 launches' session and hitch logs are kept (SessionLog / KeepLaunches; 0 keeps all). Older ones are deleted once per launch, just before FrameCare writes its first log of that launch. SessionLog / Enabled = false stops both the session log and the hitch log's file below; F2 reports still count and list hitches.

The hitch log

While SessionLog / Enabled is on (the default), every frame slower than Diagnostics / HitchMs (50 ms) is written to BepInEx/FrameCare/sessions/hitches_<date>.csv, next to that launch's session log: the time, scene, players, frame time, how long each part of the frame took, its five slowest steps, garbage collections in that frame and that second, and what happened in the second before: another player's avatar appearing, an outfit being applied, a scene load, an F2 report or an experiment starting. Frames in which FrameCare itself was busy (taking a report, applying an experiment step) are marked, so they are not mistaken for the game's. mouse_moved says whether the mouse moved in that frame. A frame counts when its time plus the time between frames before it is over HitchMs, and the columns after mouse_moved give that time between frames (between_ms), the time between phases (gaps_ms, with the largest gap), time in neither (normally 0; a hitch in which it is the largest part names it as its top phase), mouse and input events, whether the window had focus, how much of the frame and of the time before it the main thread spent on the CPU (near 0 means it waited, for example for the render thread or a frame-rate limit, or did not run), and garbage collections between frames and inside the frame (gc_in_frame and gc_between; gc_frame counts both). Losing and regaining window focus are logged as events. Something that happened just before a hitch did not necessarily cause it. The F2 report sums the log up under "Hitches in this session".

The t_s column counts from the same moment as the session log's t_s, so a hitch can be found in the session log by that number (if the session log could not be opened, it is the time since the game started); game_time_s is always the time since the game started, and the clock columns of both files match too. The file is appended at most once a second, also while every frame is slow, and takes at most 20,000 lines per session: hitches beyond that are still counted in the report, which then says how many are not in the file. Waiting for a frame-rate limit or vSync counts in a frame's time, so with the game's 30 fps limit keep HitchMs above 34, or every frame is logged.

Configuration

BepInEx/config/com.sleepeydev.ontogether.framecare.cfg, created on first launch. Changes made while the game runs (for example in a config manager) apply straight away, except SessionLog / KeepLaunches, which is used at the next launch.

FrameCare's shortcuts need exactly the modifier keys they list: Shift+F2 does nothing while Ctrl is also held, and F2 takes no report while Shift, Ctrl or Alt is held. Keys you walk with do not matter. A setting written as F2 + LeftShift + LeftControl means Ctrl+Shift+F2; either left or right key works.

Key Default What it does
Boost / OutlinePass true The outline pass boost: same picture, much higher frame rate in dense views
Boost / LodBiasFactor 1.0 Multiplies the game's LOD bias (0.25–1.0); lower is faster but simpler models appear closer
Boost / HideSmallFarObjects false Stops drawing small objects far away; faster, but they visibly disappear
Boost / SmallRadius 0.75 For the setting above: below this size (m, 0.1–3) an object counts as small
Boost / FarDistance 35 For the setting above: beyond this distance (m, 10–200) small objects are hidden
Boost / FrameRateCap 600 Caps the frame rate while vSync is off (0 = no cap, 30–1000); a lower limit set in the game stays
Boost / RemoteControllerToggleOff true Other players' avatars: no CharacterController toggle around network moves
Boost / HiddenTailSoftBoneOff true Other players' avatars: tail physics paused while no tail is worn
Boost / RemoteGroundCheckKinematic true Other players' avatars: ground-check bodies stop colliding with the ground
Boost / MenuLookupCache true The sound manager's search for the main menu answered from the game's cache on the island
Boost / AmbientSoundLookupCache true The shore and river sound positions reused while you move less than 2 cm, for at most 0.1 s
Report / Key F2 Takes a report
Report / ShortWindowSeconds 5 The recent frame-rate window; counters and timers use it too
Report / LongWindowSeconds 30 The longer frame-rate window
Report / GroupDepth 3 How many hierarchy levels name a group
Report / TopGroups 40 Groups listed in the text report (the JSON has all of them)
Report / TopMeshes 30 Meshes listed in the text report
Report / NoticeSeconds 3 How long the notice stays up
SessionLog / Enabled true Write the per-second session log and the hitch log's file (F2 still counts hitches when off)
SessionLog / KeepLaunches 20 How many game launches' session and hitch logs are kept (0–1000, 0 = all)
Experiments / RunKey F2 + LeftShift Starts or cancels an experiment run (either Shift works)
Experiments / PlayersRunKey F2 + LeftShift + LeftControl Starts or cancels a players run (Ctrl+Shift+F2; either Ctrl and either Shift works)
Experiments / SettleSeconds 1.5 Wait after each change before measuring
Experiments / MeasureSeconds 3 How long each step is measured
Experiments / SmallRadius 0.75 Experiment steps: below this size (m) an object counts as small
Experiments / FarDistance 35 Experiment steps: beyond this distance (m) an object counts as far
Experiments / LodBiasFactor 0.5 Steps 5, 10 and 12 multiply the LOD bias by this
Experiments / OutlinePassToggleKey F2 + LeftControl Switches the outline pass for this game session (either Ctrl works)
Diagnostics / MainThreadProbes true Timestamps between the steps of the engine's frame, for the main thread breakdown and the hitch log
Diagnostics / HitchMs 50 Frames slower than this (ms, 20–1000) go into the hitch log (its file only while SessionLog / Enabled is on); keep it above a frame-rate limit's frame time

To move the players run to another key, write for example PlayersRunKey = F7 + LeftShift + LeftControl. Avoid Alt: Alt+F-keys belong to the NVIDIA overlay and Left Alt+Shift switches the Windows keyboard layout.

Cost

The outline pass adds no draws: it draws what the game drew, in a different order. Hiding small far objects checks a few hundred objects per frame against the camera, five times a second, and looks for new objects every 10 seconds, spread over frames. Recording frame times, engine counters and the session log costs nothing you can measure. A report reads the whole scene in one frame, which is a visible hitch of a fraction of a second — the report says exactly how long it took. That frame is excluded from the frame-rate figures. Reading the CPU threads adds two tiny readings, one at the key press and one two seconds later, and nothing in between. The busy-server boosts check a few other players' avatars every frame (each one about twice a second) and look for avatars only when a setting is switched on or a scene loads; the menu lookup cache adds a few cheap checks where the game did a full search, and the ambient sound cache a few comparisons where the game walked two long curves. An experiment run has a short hitch when a step is applied; it falls in the wait before measuring. The frame-rate cap checks two settings once a second.

The main thread timestamps cost a few hundredths of a millisecond per frame in total: on the test machine about 132 timestamps per frame of about 265 ns each, about 0.035 ms, under 1% of the main thread on a 23-player server. Every F2 report states the measured figure. The first and last timestamp of each frame also read the main thread's CPU time (well under a thousandth of a millisecond), calibrated once with a 0.15 s busy loop on a worker thread when the timestamps go in, and the game's input events are counted as they are processed. The timestamps themselves allocate nothing per frame; the session log sums them up once a second, which allocates a little. The hitch log adds one comparison per frame; each hitch costs a small record and one line in its file, written at most once a second and at most 20,000 lines per session. The script timers exist only for the few seconds after F2. Adding and removing them costs about 1-2 ms per script, spread over the frames after F2 (at most about 8 ms of it in one frame), so a report takes a little longer than two seconds to save. The report states the totals, those frames are left out of its frame-rate figures, and the hitch log marks them as FrameCare's.

Compatibility

Built against On-Together v1.1.8 and BepInEx 5.4.23.5. Works alongside Day and Night, BlueSage QoL Tweaks, Local Playlist, Pomodoro Sound and Pomodoro Notify; turn BlueSage's Player Render Saver off for a players run. FrameCare changes no other mod (for a few seconds after F2 it times their scripts, see above). It patches the game in a few small places: when an avatar is spawned and when an appearance is applied (to note the time for the hitch log and to hand other players' avatars to the busy-server boosts); after the game applies its frame-rate setting (while a frame-rate cap is set); after it applies its quality settings (when LodBiasFactor is below 1.0); the sound manager's main menu lookup (while MenuLookupCache is on, or during a players run's step 6; with the cache off the game's own lookup runs); the shore and river sound placement (while AmbientSoundLookupCache is on, or during step 7; with it off the game's own lookup runs); and for a few seconds after F2, timers on every running script and on Unity's scene searches, removed before the report is saved. Other mods' scripts are only timed there, never changed. The busy-server boosts change settings on other players' avatars on your computer only. It also adds its timestamps to the engine's frame loop the same way Unity's own packages add their steps, and takes only its own out again. Day and Night re-applies some camera and shadow settings every second, which can undo an experiment step — the report marks those steps. F1 opens Pomodoro Notify's window; Local Playlist uses F3.