Releases: timbermods/LateGamePerformance
Release list
0.4.17 preview
Preview of Late Game Performance for Timberborn 1.1.2.4. Not yet played. The latest release is still 0.4.14. Four changes on top of 0.4.16, all outside the simulation, all from the same 35-minute Performance Log recording of a 354-beaver colony. (The zip was replaced once, a few minutes after first publishing, to add the fourth change; if you downloaded the first one, download again and check the SHA-256 below.)
Multiplayer: every player installs this same version. Nothing in this version changes which ticks run or what they compute; memory, sound and the interface may differ between players anyway.
The memory clean-up after a save moves into the save's own frame
A save allocates a lot: the snapshot of the world, then the JSON tree and the compressed bytes. In the recording every autosave was followed by a garbage collection within about ten ticks, so each save was two hitches: the save frame, then a collection frame of 100 to 190 ms a second later. From 0.4.17 a collection runs right after the save's main-thread part, inside the frame that is long anyway. The allocation budget then starts over, and the rest of the save's garbage should no longer tip it over on its own.
- One
SaveCollect:line per save says how long the collection took and how much it freed. - Whether the second hitch is gone shows in the
Timing:line, which counts the save frame with its collection separately from ordinary collections. Please send a few of those lines. - Also runs after the save BeaverBuddies writes for a joining player (one hitch on the host at join).
CollectAfterSave = falseinLateGamePerformance.cfgswitches it off.
The audio listener is placed only when needed
Every frame the game casts a ray from the screen centre against the terrain and against every block object to find what is under it, then moves the audio listener a tenth of the way there: about 0.3 ms per frame and 50 KB/s of garbage in a large colony. Now it runs when the camera moved, while the listener is still gliding towards its target, and otherwise once every ten frames, so something built under the screen centre is picked up within a fraction of a second. Sound only. SoundListener = false switches it off; the SoundListener: stats line says in how many frames it ran.
Two interface systems stop redoing their work every frame
- The status aggregator goes through every status in the colony every frame to fill the alert lists the top bar reads, about 0.35 ms per frame. It runs every fourth frame now; a status that appears or clears reaches the alert count a few frames later. Removing a subject still updates the lists at once.
- The selected entity's panel refreshed every fragment every frame, the largest single source of garbage among the game's systems at about 120 KB/s. It refreshes every second frame now, and always on the frame a different entity is shown.
Neither is read by the simulation. UiThrottle = false switches both off; the UiThrottle: stats line counts them.
Animated objects off screen keep their time but skip the pose
Every frame the game advances every Timbermesh animator in the colony and writes its pose: node animators move child transforms, vertex animators set a material time. In the recording that was about 1.1 ms per frame, and twice that in the working day when every beaver is out walking, whether or not the object was on screen. Now, when none of an object's renderers is visible (shadow casters count as visible), the mod runs the game's own time-keeping and leaves out only the pose writes. Time, the finished flag, the animation-changed event and the wonder's saved animation time are exactly what they would have been; when the object comes back into view its pose is written again from the time it would have had anyway. On the single frame in which it first reappears it can show the pose it had when it left the screen.
Rendering only: the simulation reads animator time (the wonder, the clutch, particle triggers, the character model), never a node transform or a material. AnimatorCulling = false switches it off; the AnimatorCulling: stats line counts the updates left out.
What was tested
244 checks pass against the installed game's assemblies, including all 84 patch targets.
- Save collect: one collection per save, never from inside itself, off for the session after a failure.
- Listener rule: runs on the first frame, while gliding, at once on a camera move, and one frame in ten with a still camera.
- Interface rule: 25 status and 50 panel updates in 100 frames, and a newly shown entity on its first frame.
- Animator culling: the patch target and every private accessor it needs resolve against the game's assembly; the visibility rule itself needs a running game.
- Not tested: the game running with any of the four. The test harness cannot apply patches. If the alert counts or the entity panel look wrong,
UiThrottle = falseis the way out.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.17.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
SHA-256 of the zip: a6f123a7e525c16bbf6f39ef854485d120a9e2883df8e326ce72d9b3e696f45c
0.4.16 preview
Preview of Late Game Performance for Timberborn 1.1.2.4. Not yet played. The latest release is still 0.4.14. One fix and one measuring tool, both from reading a 35-minute Performance Log recording of a 354-beaver colony at speed 7 on 0.4.14.
Multiplayer: every player installs this same version. Nothing in this version changes which ticks run or what they compute.
After a long frame the simulation no longer catches up all at once
The game turns each frame's Time.deltaTime into simulation buckets, and Unity caps that delta at a third of a second. So after a 300 ms frame (an autosave, a garbage collection) at speed 7 the next frame is asked for 2.1 s of game time, three and a half ticks. In the recording every autosave was followed by frames of 131, 66, 142, 290 and 107 ms, and every collection frame by a similar tail: one hitch became about a second of stutter.
From 0.4.16 one frame may catch up at most twice the recent ordinary frame time (at least a thirtieth of a second); the rest is simply not run, so the simulation loses a fraction of a second of wall clock per hitch. Which ticks run, and in what order, is unchanged, and how many buckets one frame runs already differs between players and machines, so this is pacing, not simulation.
- The limit follows each machine's own frame time. A computer that always needs 70 ms per frame keeps its full share; a guest catching up in BeaverBuddies at a raised speed still catches up.
- It hooks
Ticker.Update, which BeaverBuddies leaves to the game (its own patch there only marks that ticking is in progress). - Setting
LimitCatchUp = falseinLateGamePerformance.cfgswitches it off. ACatchUp:line is logged with the stats whenever it did something: how many frames were limited, how much game time was left out, the longest such frame and the ordinary frame time it measured.
Diagnostics now time the game's own path searches
The same recording shows single beaver ticks of 6 to 14 ms, and the frame rate at its lowest at the end of every game day. By reading the game's code, the suspect is the path search the game falls back to when no route map answers: a beaver standing off the road network (a field, a forest) pricing every building that could satisfy a need, or walking to a random spot, runs a full A* search on the main thread inside its own tick, once per building priced. That is not measured yet, so this version measures it.
With Diagnostics = true in the cfg the Diagnostics: line now also reports need selection, walker path finding and both A* searches: calls, total time, the longest single call, and for the searches how many were real searches rather than answers from the previous one, how many nodes they explored and how many explored everything reachable. It adds overhead to hot code; switch it back off after one session. Please send those lines together with the CatchUp: and Timing: lines from a big colony.
What was tested
230 checks pass against the installed game's assemblies, including all 79 patch targets.
- The catch-up rule: ordinary frames pass unchanged, a 300 ms frame after 16 ms frames is cut to a thirtieth of a second, a steady 70 ms machine keeps its full share, paused frames are left alone, an exception switches the feature off and leaves the game's delta untouched.
- Not tested: the game running with the limit. The test harness cannot apply patches.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.16.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
SHA-256 of the zip: 12ef031422c1a0ad094dca319231c2cb89b840970961fc0bfb5c71f35e7d09c5
0.4.15 preview
Preview of Late Game Performance for Timberborn 1.1.2.4. Not yet played. The latest release is still 0.4.14. Two fixes found in a Performance Log recording of a 354-beaver colony on 0.4.14.
Multiplayer: every player installs this same version. Both changes give the game's exact results. (In one session a friend was still on 0.4.13 while the host had 0.4.14; please update together.)
District resource counts now work with MixedStorage installed
Before sending a count to worker threads, the district count feature checks whether another mod patches something the workers call. If one does, the feature stands down and the game counts on the main thread. MixedStorage patches SingleGoodAllower.AllowedAmount for its multi-good warehouses and piles, so with MixedStorage installed the feature never ran. That is about 1 ms per tick of counting on the main thread in the recorded colony.
MixedStorage 0.5.7's patch (LimitPatch.Prefix) was read. It answers from the allocation the player set and the storage's capacity. The only thing it writes is its own per-storage cache of limits, rebuilt from those same values, and each storage is counted by exactly one worker. So it is accepted.
- The check is exact: mod id, patch method and target method together. Any other patch, including a new one from a later MixedStorage version, still hands counting to the game.
- The log says:
DistrictCounts: another mod patches SingleGoodAllower.AllowedAmount (kyler.mixedstorage, MixedStorage.LimitPatch.Prefix); that patch was read and is safe to run on worker threads, so counting stays on them.
The water map copy no longer hides the map from other mods' postfixes
0.4.14 made the water map copy on a worker thread and swapped it in by skipping ThreadSafeWaterMap.Tick. That also skipped ThreadSafeWaterMap.Update and every mod patch on it. BeaverBuddies has one: with its Debug setting on, its desync trace hashes the water map after Update, and under 0.4.14 that hash was missing on almost every tick. From 0.4.15 the mod hooks Update itself. Harmony still runs other mods' postfixes on a method whose prefix skipped it, so those postfixes now see the swapped map, the same bytes the game's own copy would be. Without BeaverBuddies' Debug setting nothing changes.
What was tested
All tests pass against the installed game's assemblies (73 patch targets).
- The water map copy: the same byte-for-byte comparison with the game's own copy as in 0.4.14, now through the
Updatehook. - The district count check: MixedStorage's patch is accepted and logged. A different MixedStorage patch, the same patch name under another mod's id, and the reviewed patch on a different method are all refused.
- Not tested: MixedStorage's patch itself running on worker threads. The test harness cannot apply patches.
DistrictCountsVerify = truemakes the game count as well and compare;verify mismatches 0in theDistrictCounts:line is the check.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.15.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
SHA-256 of the zip: 80094c9c20d011d06c2a0a2eef2e9a5a1349e93b79c2e320cae6386dd4ed853b
0.4.14
Late Game Performance for Timberborn 1.1.2.4. Played in-game and reported as working well. This release includes everything since the previous release, 0.4.9: 0.4.10 to 0.4.13 (see their release notes: one settings checkbox, faster tree search when lumberjack flags are full, plant water checks, terrain route maps and district counts on worker threads, background saves) and the new parts below.
Multiplayer: every player installs this same version. That is all. The two new parts that touch the simulation give the game's exact results and are not settings; the rendering part never touches the simulation.
In the test harness the water copy and the soil scans together take about 1.8 ms per tick off the main thread on a 256 x 256 map. How much that is in the game depends on the map and on Unity's runtime; the stats lines below will say.
New: the water map copy is made on a worker thread
Every tick the game copies the whole water map into the copy that other threads read (ThreadSafeWaterMap.Tick): 0.70 ms per tick in the measured colony. What that copy will hold is known as soon as the water simulation's last task has finished, and nothing changes the simulation's arrays after that until the next tick. So the mod makes the same copy right then, on that worker, into a second set of arrays (the flow directions with the game's own calculator), and in the next tick the main thread only swaps the two sets.
- Nothing reads the second set while it is being written: the parallel tasks read the set that was current when their tick started, and the main thread reads through the map's own fields, which only change in the swap.
- The game makes its own copy, as without the mod, on any tick where the arrays may have changed after the copy: a queued building, dam or terrain change was applied to the water layout, the simulation was reset, a water layer was added, the game flags a column change, or no copy was ready.
- Harness, 256 x 256 map with 3 water layers: the game 0.83 ms on the main thread; the mod 0.54 ms on a worker and under 0.001 ms on the main thread.
New: soil moisture and contamination scans skip tiles with nothing changed
Every tick the game walks every tile and every soil layer twice (moisture, contamination) to find the few cells that changed. The mod walks them in the same order but reads eight tiles' change flags at once and skips the group when none is set; for the rest it runs the game's loop and the game's own per-cell method. Same cells, same order, same values. Harness, 256 x 256, 3 layers, 1 cell in 500 changed: 0.59 ms → 0.10 ms per scan.
New: water rendering stops re-doing work that changes nothing on screen
Rendering only. WaterRendering = false in LateGamePerformance.cfg turns it off.
- The game switches every water tile off and the ones with water back on, every tick. The mod only switches tiles whose state changes.
- For flow directions and flow limits, the game re-uploads each tick data the graphics card already holds. The mod leaves that upload out only when it can show it is identical: same texture, same array, exactly one data swap since the card got it. Otherwise the game uploads as usual. Depths, contamination, columns and link barriers are left alone.
- Not done: skipping empty water layers. It would need whole layers compared, which costs about what the upload does.
New log lines
[LateGamePerformance] Simulation features: HaulCache on, RouteMaps on, YielderSearch on, TerrainMaps on, PlantWater on, DistrictCounts on, WaterMapCopy on, SoilScans on. These are the same for every player on this version.
[LateGamePerformance] Last 1000 ticks. WaterMapCopy: 996 ticks swapped in the copy made on a worker thread (0.540 ms there per tick); the game copied on the main thread in 3 ticks where the water layout changed and 1 where no copy was ready
[LateGamePerformance] Last 1000 ticks. SoilScans: 2000 moisture and contamination passes in 200.0 ms (0.100 ms each); 97.0% of 8-tile groups had nothing changed and were passed over; 5400 changed cells updated
[LateGamePerformance] Last 1000 ticks. WaterRendering: 1000 updates switched water tiles 800 times where the game switches them 900000 times; 3000 of 6000 flow direction and flow limit uploads left out because the graphics card already had them
(The numbers are illustrations, not measurements.)
What was tested, and what was not
The harness passes against the installed game's assemblies; 73 patch targets resolve (15 new), none with an exception filter.
- Water map copy: two real
ThreadSafeWaterMaps over one realWaterSimulator, one ticking the game's way and one through the mod, for 16 ticks with water, flows and column counts moving, a layout change, a reset, a new water layer and a missing copy: the same bytes in both after every tick. Verify mode compares the same way: 0 mismatches. - Soil scans: the order of cells matches the game's loop on 90 random maps; over the real soil services, the game's per-cell method is called for the same cells, coordinates and levels in the same order. (The game's per-cell method cannot itself run in the harness, because the soil texture map it updates calls into Unity.)
- Water rendering: only that its patch targets resolve. It needs Unity's renderer, so it has not been tested at all.
- In the game: played and reported as working well. Not yet read: the new stats lines from that session, so the in-game savings are still unmeasured. Worth checking in your own game:
- The startup line shows
WaterMapCopy on, SoilScans on, and the log hasWaterTiles: enabled (3/3 patches)andWaterUploads: enabled (5/5 patches). - Water looks normal: surfaces, edges, waterfalls, flow patterns, contamination colour, and water appearing and draining. Take before/after screenshots of the same spot if you can. If anything looks wrong, set
WaterRendering = falseand sendPlayer.log. - The
WaterMapCopy:line mostly says "swapped in". For a check,WaterMapCopyVerify = trueandSoilScansVerify = trueshould giveverify mismatches 0. - In BeaverBuddies, the usual per-tick fingerprints stay identical on both computers.
- The startup line shows
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.14.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
SHA-256 of the zip: 3586c4bb2f404fd94964587303bb801a424685a873b0139bebad2bfd582ec5f4
0.4.13 preview
Preview of Late Game Performance for Timberborn 1.1.2.4. Nothing new in this version has been played in-game yet. The latest release is still 0.4.9. This preview includes everything in 0.4.10 to 0.4.12, which have been played.
Multiplayer: every player installs this same version. That is all. Nothing here is a setting that could differ in a way that matters.
Because this version touches saving: keep a manual save from before you install it, and after the first autosave, check that Player.log has a BackgroundSave: ... written line and that the autosave loads.
New: autosaves and menu saves finish on a worker thread
A save froze the game for 0.8 s on a fast computer and up to 2.4 s on a slower one in the colony this is measured in. About a third of that is the snapshot, which reads live game objects and has to stay on the game thread. More than half is turning the snapshot into JSON, compressing it and writing the file, and none of that needs the game: the snapshot is plain data from the moment it is taken. That half now runs on a worker thread while the game carries on.
It works inside the game's own save instead of replacing it:
GameSaver.Saveis not patched. BeaverBuddies wraps it (to move saves to a tick boundary), and that keeps working because everything this does happens inside it.- On the game thread, in the game's order: the snapshot, the thumbnail, the metadata, and any entry another mod adds. Only the world entry's JSON is left for the worker.
- The worker builds the archive exactly as the game does (same zip mode, entry order, compression level), reads it back and checks every entry, writes it to
<name>.timber.saving, flushes it to the disk, and only then renames it over the target. The game lists saves by.timber, so an unfinished file is never offered for loading, and a save you overwrite stays intact until its replacement is complete (the game itself truncates the old file first and then writes). - The game's completion callback (for an autosave: delete the oldest autosaves) runs on the game thread only once the new file is in place. If a save cannot be written, no old autosave is deleted because of it.
What is deliberately left alone: the save on exit, BeaverBuddies' rehost save, and the saves BeaverBuddies streams to a joining player. Those callers expect the result on return, so they run as the game's own code.
What happens when something goes wrong:
The .saving file cannot be opened, another mod supplies the file service, or the save is not the queued one |
written into the game's own stream, there and then, the game's errors included. A prepared save never ends as an empty file. |
| The worker fails (disk, rename refused after five tries, anything thrown) | saved again on the game thread from the same snapshot, the game's way; background saving stays off for the session |
| That fails too | SAVE FAILED in the log, the game's callback does not run, the game gets the same GameSaverException it would have had |
| Preparing fails | nothing has been written; the feature turns itself off and the game performs that save in full |
| Another save, a new scene, quitting, or anything asking the save list about files while a save is being written | waits for it first (the worker is a foreground thread, so the process does not end under it) |
BackgroundSave = false in LateGamePerformance.cfg switches it off. It does not touch the simulation, so it does not matter in multiplayer who has it on.
New: district resource counts are added up on worker threads
Every tick each district adds up, for every good, what all its storage holds and how much room it has: 1.07 ms per tick in the measured colony. The numbers feed the top bar, the tooltips and, inside the simulation, the automation resource counter, so they have to be exactly the game's numbers at the game's moment. Counting less often or only what changed was looked at and dropped for that reason.
Both questions are pure reads of an inventory, and the answer is a sum of whole numbers, which comes out the same in any order. So the inventories are dealt out to worker threads, each asks the game's own questions and adds up into its own table, and the main thread adds the tables into the game's. An inventory whose capacity rule comes from a mod is counted on the main thread; if another mod patches a method the workers would call, the feature stands down with one log line and the game counts. The result is the same either way and on every computer.
New log lines
[LateGamePerformance] Simulation features: HaulCache on, RouteMaps on, YielderSearch on, TerrainMaps on, PlantWater on, DistrictCounts on. These are the same for every player on this version.
[LateGamePerformance] Save: 310 ms total = finishing the tick 14 ms + snapshot 190 ms + world JSON and compression 0 ms + thumbnail 60 ms + everything else 46 ms (world JSON, compression and the file write follow on a worker thread)
[LateGamePerformance] BackgroundSave: 2026-09-20 21h14m, Day 412.autosave.timber written (14.2 MB). Off the game thread: world JSON, compression and check 540 ms, file 35 ms. On the game thread: snapshot and the other entries 262 ms.
[LateGamePerformance] Last 1000 ticks. DistrictCounts: 1000 counts of 640 inventories on 7 workers in 310.0 ms (0.310 ms each); 0 inventories per count on the main thread; 0 counts of small districts left to the game
(The numbers are illustrations, not measurements.)
What was tested, and what was not
The harness passes against the installed game's assemblies; 58 patch targets resolve (11 new), none with an exception filter.
- The file writer, with real files: same entries, order, contents and size as the game's writer; an overwritten save readable until its replacement is complete; a refused rename retried; a rename that never works, a writer that throws, and a world entry that comes out empty all leave the old save untouched, and the second attempt behaves as described. (The tests caught one real bug here before release: an archive that had failed its check could have been reused by the second attempt.)
- The game's real
WorldSerializerwrites the same bytes on a worker thread as on the main thread for a 4000-entity world, and the game's own reader loads what the worker wrote, every entity in order. - The whole chain of hooks in the order
GameSaver.Savecalls them, with a realSaveWriterand the realWorldEntryWritertype: the callback runs once, on the game thread, with the complete file in place; a second save waits for the first; instant saves and saves to a stream are the game's; a save that is not the queued one, a foreign file service and a blocked.savingname all end as complete files on return; a doomed save raisesGameSaverExceptionand never reports completion; a new scene finishes the file and drops the old scene's callback. - District counts: 700 real
Inventoryobjects with the game's own capacity rules plus one stand-in mod rule, six rounds with stock moving in between, every good compared through the game's publicGetResourceCount; the mod rule was only ever asked on the main thread. In the harness one count takes the game 1.9 ms and the mod 0.5 ms. - Not tested: anything inside the game. Three things can only be seen there:
- That the first autosave produces the
BackgroundSave: ... writtenline and loads. - In a BeaverBuddies session, whether its wrapper leaves
GameSaver.Saverecognisable as the caller. If not, saves simply stay the game's own and one line saysBackgroundSave: a queued save is pending but the writer was called by '...'. Send me that line and it is a one-line fix. - The
DistrictCounts:"ms each" figure under Unity's runtime, to compare with 1.07 ms.DistrictCountsVerify = truemakes the game count as well and compares;verify mismatches 0is the line to look for.
- That the first autosave produces the
If anything about saving looks wrong, set BackgroundSave = false, or go back to 0.4.12, and send Player.log.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.13.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
SHA-256 of the zip: 683c7afa8831b56efb6a9f0cabe4ab24e14463a96ab0566ea7a93f15cfd06a59
0.4.12 preview
Preview of Late Game Performance for Timberborn 1.1.2.4. Nothing new in this version has been played in-game yet. The latest release is still 0.4.9. It includes 0.4.10 (one checkbox on the settings page, played) and 0.4.11 (the tree search skips goods the building has no room for, not yet confirmed in a game).
Multiplayer: every player installs this same version. That is all. Nothing here is a setting that could differ.
New: the water check on every plant is read on worker threads
Every tick the game asks, for every plant and every other object that cares about flooding (about 8000 in the colony this is measured in), how high the water stands at its tile, one after another on the main thread: 1.3 ms per tick, 8% of all tick time. Almost none of them change from one tick to the next.
The question is a pure read of a water map the game already keeps safe for other threads, and which is never rewritten during this step. The reads are now spread over worker threads; then the main thread goes through the results in the game's own order and, for each object whose level changed, does exactly what the game does, so a plant starting to drown or a building flooding happens on the main thread, in the same order, with the same values. The result does not depend on the number of threads, so it is the same on every computer.
New: terrain route maps are rebuilt on worker threads
Every building that works the land around it (lumberjack flag, gatherer, farmhouse, forester...) keeps a second route map next to its road one, over open ground, 20 steps out. Every search for a tree or a plant reads it. When the ground changes inside one, the game rebuilds it on the main thread the next time its building searches.
Now every such map that is not built gets built at the end of the navigation tick, with the game's own generator on worker threads, and the main thread waits for the batch. It is a few tens of small maps (in the test harness 90 maps take 32 ms one by one and 7 ms on 7 workers), so this was built as a short wait, not as a background rebuild with gates like the road maps have. How often the ground changes in a real colony, and so how much this saves, has not been measured. The new TerrainMaps: stats line says how many were rebuilt and how long the main thread waited; if that wait ever matters, the background scheme can go under it.
Like the road maps, it builds maps before the first request instead of on it, so the few code paths that use a terrain map only "if it is already filled" find it filled. Every player on this version gets the same.
Removed: the hauling cache's district list
The cache kept each building's hauling jobs, and on top of that the district's whole sorted list until any building changed. In every session measured, on two computers, that list was never served from cache once: a hauler that takes a job reserves stock, which changes a building. It was code and bookkeeping with no effect. The per-building part, which is reused about a third of the time, stays. The HaulCache: stats line now reads 517 hauler list requests built in 310.0 ms (0.600 ms each); buildings reused 42010/129250, recomputed 87240.
New stats lines and startup line
[LateGamePerformance] Simulation features: HaulCache on, RouteMaps on, YielderSearch on, TerrainMaps on, PlantWater on. These are the same for every player on this version.
[LateGamePerformance] Last 1000 ticks. PlantWater: 1000 passes over 8100 objects on 7 workers; reading 240.0 ms (0.240 ms per pass), 310 level changes applied in 1.2 ms
[LateGamePerformance] Last 1000 ticks. TerrainMaps: 3 rebuilds of 96 terrain route maps on 7 workers; the main thread waited 9.5 ms in total, longest 4.1 ms; 5 more built directly in batches of fewer than 4
(The numbers in the last two are illustrations, not measurements.)
What was tested, and what was not
The harness passes against the installed game's assemblies; 47 patch targets resolve (4 new).
- Water: two identical sets of 6000 of the game's real
WaterObjects over a stand-in water map that moves between ticks; one set goes through the game's own loop, the other through the mod. Same stored levels and the same events, in the same order, with the same values, for six ticks (4316 level changes). A handler that throws switches the feature off and hands the tick to the game without applying anything twice. - Terrain: a terrain graph built with the game's
TerrainNavMeshGraph; 90 maps filled one by one and in parallel with the game'sTerrainFlowFieldGeneratorare identical node for node and in node order, a second batch on the same workers too. Against a realTerrainFlowFieldCache: everything cached gets built, a map whose start is off the graph is left alone, a second tick builds nothing, and after a ground change exactly the cleared maps are rebuilt. - Not tested: nothing here has run inside the game. Two things can only be seen there: that reading on worker threads is actually faster than the game's loop under Unity's runtime (the
PlantWater:line gives the read time per pass, to compare with 1.3 ms), and how often terrain maps are rebuilt.PlantWaterVerify = trueinLateGamePerformance.cfgre-reads everything on the main thread and compares;YielderSearchVerify = truedoes the same for the tree search. One session with both andverify mismatches 0in the stats lines is worth having.
If the game misbehaves, go back to 0.4.9 and send Player.log.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.12.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
SHA-256 of the zip: 62419d451f1033703bd52b465c077736e74ef77f8d04e61acd99c22f669d81d3
0.4.11 preview
Preview of Late Game Performance for Timberborn 1.1.2.4. The change in this version has not been played in-game yet. The latest release is still 0.4.9. It contains 0.4.10 (one checkbox on the settings page), which has been played.
The tree search no longer measures every grown tree for a building that is full
A session on 0.4.9 showed the tree and plant search still cost 4.3 ms per tick on a fast computer, about 5 searches per tick over roughly 1300 trees each, with only 7% of the distance lookups left out. The game's own per-component report for the same session puts lumberjack job selection at 3.8 ms per tick, 22% of all tick time and the largest single item in the simulation.
The save explains both numbers. 6208 of its 6263 cuttable plants are grown, so the 0.4.8 rule (skip plants with nothing to take) had almost nothing to skip. And all 20 lumberjack flags hold 20 logs, which is full, so the 18 lumberjacks ask for work again on every decision, and each time the game measures the path distance to every unreserved grown tree on the map before concluding there is nothing to do.
The game's last step takes the closest plant of each good and accepts the first whose carry amount is above zero. That amount is the smallest of what the worker can lift, what the plant yields, and the room left in the building; the first two are at least 1 for any grown plant, so the amount is zero exactly when the building has no room for that good, whichever plant it is. The distance to plants of a good there is no room for therefore cannot change the answer, and from 0.4.11 those lookups are left out. The mod asks the game's own carry calculation, once per good per search. The answer is still exactly the game's. A full flag among 1300 grown trees needs one lookup instead of 1300. When there is room, every grown tree is looked up as before and the same tree is chosen.
The YielderSearch: stats line now also says how searches ended, for example outcomes: 12 found work, 5188 found nothing the building has room for or the worker can take, 0 found nothing in range, so a colony whose lumberjacks are stuck behind full flags can be seen at a glance.
This does not make the lumberjacks less stuck: that is log storage and hauling, and a gameplay matter. It makes them cost almost nothing while they are.
What was tested, and what was not
The harness passes against the installed game's assemblies (43 patch targets, unchanged). The rule is checked against a model of the game's whole search, result included, on 4000 more random forests with full and part-full buildings, several goods, unreachable, dead and destroyed plants and ties: 0 differences. The measured case and its opposite (room available) are separate checks. The model is mine, read from the game's code; YielderSearchVerify = true in LateGamePerformance.cfg compares against the game's real search while playing, and one session with verify mismatches 0 in the stats lines is what proves it on your save. Nothing in this version has run inside the game.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.11.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
Multiplayer: every player installs this same version. That is all.
SHA-256 of the zip: 70cc11a12457ed348d220f9377a347afae876fc208110630a0eb848980386b6b
0.4.10 preview
Preview of Late Game Performance for Timberborn 1.1.2.4. Not yet played in-game. The latest release is still 0.4.9. Nothing in this version changes what the game simulates, and it is compatible with 0.4.9's behaviour in every way that matters for multiplayer, but as always every player should run the same version.
The settings page has one checkbox
The mod is meant to take care of things for the player. After 0.4.9 fixed everything that affects the simulation, four boxes were left on the page under Mods > Late Game Performance, and only one of them is a decision a player should have to make.
- Incremental garbage collection stays. It edits a file in the game's install folder, and that should not happen without being asked. It is also the single biggest improvement the mod has made. Players may differ on it: it only changes when the game pauses to clean up memory, never what happens in a tick.
- Warn when garbage collection is not incremental is gone. The main menu message now asks once: Not now is remembered (in the game's own settings store, so it survives updating the mod), and the checkbox remains for anyone who changes their mind. 0.4.7 to 0.4.9 asked once per launch.
- Adaptive garbage collection pacing (experimental) is removed, feature and all. One multiplayer session each way, on two computers, showed no difference that could be told from noise, and a player has no way to judge it. One per-frame hook fewer. If you had it ticked, nothing needs doing: Unity's normal 3 ms slice applies from the next launch.
- Record per-component timings moved to the settings file as
RecordTimings = false. It is a profiling tool that slows the game a little while it is on. If you had that box ticked, it is now off, so the game's per-component timers no longer run; setRecordTimings = trueinLateGamePerformance.cfgand restart to profile again. Launching with-metricsstill works.
Still there, unchanged: if the incremental collection box is ticked and a game update restores boot.config, the mod puts the line back, and every Timing: line ends with whether collection is incremental.
What was tested, and what was not
The harness passes against the installed game's assemblies: 43 patch targets resolve (one fewer, the pacing hook). New checks: the settings page exposes exactly one setting; the main menu question is asked only when collection is not incremental, the line is not already in boot.config, it has not been shown this launch and it was not declined before; the notice can be built from what the main menu provides, now including the game's settings store; RecordTimings parses and is off by default. Nothing here has run inside the game. The main menu message has still never been seen on screen, because the test computers already collect incrementally; if the game shows an error at the main menu, go back to 0.4.9 and send Player.log.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.10.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
Multiplayer: every player installs this same version. That is all.
SHA-256 of the zip: 64e0ea86fa13ff612200820a02c0592e3df83463a33ba030b1002bf5de1012c1
0.4.9
Late Game Performance for Timberborn 1.1.2.4. The features are the same as in 0.4.8, which was played in a late game save of about 350 beavers with noticeably fewer lag spikes; what changes is who decides about them. This exact build has not been reported as played yet; it runs the code paths 0.4.8 runs with its default settings.
Nothing that affects the simulation is a setting any more
Up to 0.4.8, four keys in LateGamePerformance.cfg decided which simulation code ran: HaulCache, HaulCacheFlushEveryTicks, RouteMaps and YielderSearch. In multiplayer every player had to keep them identical by hand, and nothing could check that for them: a multiplayer mod can compare mod versions, but it cannot see inside another mod's settings file. Two players with different values could drift apart.
From 0.4.9 they are not settings. The hauling cache, the route maps and the tree and plant search are always on, with the same fixed values for everyone (the values that were the defaults, and the only ones ever played). The same version of the mod now behaves the same for every player, and the version check a multiplayer mod already does covers it. Nobody has to know any of this.
- An older
LateGamePerformance.cfgthat still has those keys is fine: they are ignored, andPlayer.logsays so for each one. - Everything still in the file is measurement, testing, or how work is split over threads. None of it changes what the game simulates, so players may differ:
HaulCacheVerify,YielderSearchVerify,RouteMapsBackground,RouteMapsMinFields,RouteMapsWorkers,Timing,SaveTiming,MetricsEveryTicks,GcReport,Diagnostics,StatsEveryTicks. The same goes for the settings page in the game. - To run without one of the fixed features, disable the mod.
New startup line
[LateGamePerformance] Simulation features: HaulCache on, RouteMaps on, YielderSearch on. These are the same for every player on this version.
The one way two players on the same version could still differ is if a feature failed to start on one computer, for instance after a game update moved something. Then the line reads OFF for it and says to compare with the other players' logs. The hauling cache and the tree and plant search give exactly the game's results, so one of those being off on one computer changes nothing; route maps being off on one computer is the case worth noticing.
What was tested, and what was not
The harness passes against the installed game's assemblies (44 patch targets, unchanged). New checks: the four values cannot be changed from a file, no field or settable property carries one of their names (so a later change cannot quietly make one a setting again), the shipped file does not offer them, an old file's keys are reported as ignored, the other settings still parse, and the startup line. No report of this exact build running inside the game has come back yet; the code paths are the ones 0.4.8 runs with its defaults.
The zip is the file that was published as the 0.4.9 preview, so the README inside it still calls it a preview.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.9.zipintoDocuments/Timberborn/Mods. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
Multiplayer: every player installs this same version. That is all.
SHA-256 of the zip: 27a39a6152a0873c78935c3c2235a20d69c0018148dbcfb422aae71eb1291c2a
0.4.8
Late Game Performance for Timberborn 1.1.2.4.
Played in a late game save of about 350 beavers: noticeably fewer lag spikes. Whether a verify session (YielderSearchVerify = true) was run against the game's own search is not known; it is still worth one if you want the new search proven on your save.
Why this version
A multiplayer session logged on both computers showed that garbage collection is no longer what limits the slower one. It needs about 77 ms of main-thread time per tick (34 ms simulation, 3 ms for the multiplayer mod's entity hash, 40 ms of "everything else" that scales with ticks) against the 85 ms a tick may take at speed 7 with the large colony speed limit removed. It runs at about 90% of what it can do, slips behind, is asked to catch up, and the frame rate collapses over the session. So this version goes after the per-tick cost: one real saving, and a measurement of the part nobody could see into.
New: a faster search for trees and plants (on by default)
Every time a lumberjack looks for a tree (and a gatherer or a farmer for a plant), the game goes through every candidate, looks up the path distance from the building for each one, and only then asks whether the plant has anything to take. For lumberjacks the candidates are every unreserved marked tree on the map, and in a late game colony most of them are still growing. It was the largest single item in the measured colony's simulation: 2.9 ms per tick, 16% of all tick time, on a fast computer.
The distance of a plant with nothing to take matters for one thing only: whether it can be reached, and only until the search has found something reachable that is grown or alive. After that it cannot change the answer, and the mod leaves its lookup out. Plants that have something to take are looked up exactly as before, in the same order, so the answer is the game's answer, not an approximation.
- The first candidate of every search is still looked up, because that lookup is also what refills the building's terrain route map after a terrain change, and that has to happen on the same tick as without the mod.
YielderSearchVerify = trueruns the game's own search as well, compares, logs any difference and uses the game's result. Worth one session before trusting it.- If anything throws, the feature switches itself off for the session and the game's own code runs.
- Marked
(simulation)out of caution: every multiplayer peer should have it the same (YielderSearchinLateGamePerformance.cfg). - A new stats line shows what it did:
YielderSearch: 640 searches for trees and plants over 1280000 candidates; 32640 distance lookups, 1247360 left out (97%); 410.0 ms in total (0.641 ms each)(illustration, not a measurement). If a session with working lumberjacks shows 0 searches, the hook did not take; please send that log.
How much it saves depends on the forest: a lot when most marked trees are growing, nothing when every marked tree is grown.
Changed: the Timing: line splits "everything else"
everything else 30.0 ms per frame = 60% (per-frame systems of the game and mods 6.0 ms, their late-update systems 9.0 ms, the rest 15.0 ms: rendering, animation and Unity itself)
The game runs its own per-frame systems, and those that mods register, from two calls. Both are now timed; what remains is Unity. This is what should say where the slower computer's 40 ms per tick goes.
Changed: adaptive garbage collection pacing never goes below the default
One session each way (0.4.6 fixed slice, 0.4.7 paced, two computers) showed no difference that could be told from noise. On the slower computer 0.4.7 pinned the slice at 1 ms because its frames are always slow, which only makes every cycle three times as long. It is now 3 ms, or 6 ms while frames are fast and 8 ms while paused. Still experimental, still off by default.
Removed: allocation figures in the stats lines
The game's runtime does not keep the per-thread counter they needed ("the counter did not move"), so they were never shown. A separate per-entity profile answered the question instead: the whole simulation is under a tenth of all allocation, and this mod's part of it about 0.3%.
What was tested, and what was not
The harness passes against the installed game's assemblies: 44 patch targets resolve (3 new), including the new check that a patch's __result matches the target's return type. The search rule is run against a model of the game's search on 4000 random forests, with unreachable, dead and destroyed plants and ties, and must give the same answer every time; a forest of 2000 marked trees with 50 grown needs 51 lookups instead of 2000; with nothing reachable, nothing is left out. The model is mine, read from the game's code; YielderSearchVerify compares against the game's real search in a running game. 0.4.7's main menu notice has not been seen yet, because the test computers already collect incrementally.
The zip is the file that was published and played as the 0.4.8 preview, so the README inside it still calls it a preview.
Install
- Close Timberborn. Delete any older
LateGamePerformancefolder, then extractLateGamePerformance-0.4.8.zipintoDocuments/Timberborn/Mods. If you keep your ownLateGamePerformance.cfg, the new keys default toYielderSearch = true,YielderSearchVerify = false. - Requires the Harmony (2.4.1+) and Mod Settings Workshop mods.
Multiplayer: every player needs this same version and the same (simulation) settings.
SHA-256 of the zip: d7cf70b2d3e1688a9384ce230c849945f43c3214dbcdaf60a97fe74acedaa192