Releases: Zaldaryon/Optimum
Release list
v0.3.5
🔖 v0.3.5
Optimum 0.3.5 continues to target Vintage Story 1.22.5 by default and adds experimental, opt-in support for building and installing against Vintage Story 1.22.6, ahead of Anego publishing matching open-source updates for vsapi/vsessentialsmod/vssurvivalmod.
New
- make bootstrap VERSION=1.22.6 (and install-linux.sh --version 1.22.6, install-windows.ps1 -Version 1.22.6, install-macos.sh with a version-mismatch confirmation) builds and installs Optimum against the real 1.22.6 client. patches-1.22.6-bridge/ reconstructs the confirmed 1.22.6 source-level changes for the three affected fork repos (vsapi, vsessentialsmod, vssurvivalmod) on top of the pinned 1.22.5 refs, plus a one-line version-constant bump (vscreativemod and Cairo need no bridge, confirmed byte-identical to 1.22.6 at the decompiled level). The closed-source engine (VintagestoryLib/Vintagestory) needs no bridging at all: bootstrap.sh/bootstrap.ps1 decompile it fresh from whichever client archive --version/-Version downloads, so it's already 1.22.6-shaped the moment decompilation finishes.
- install-linux.sh prompts for the Vintage Story version interactively when a patches--bridge/ directory offers an alternate to the pinned default, and shows the chosen version in the install summary.
- install-macos.sh (which overlays Optimum's compiled DLLs onto an existing local Vintage Story install rather than downloading one) now detects a version mismatch between that local install and what Optimum was built for, and asks for confirmation before installing a mismatched build instead of doing so silently.
- install-windows.ps1 gained a -Version parameter, threaded through to bootstrap.ps1 and the version-requirement check that previously only accepted the exact version pinned in forks.json with no override. Both the GUI and the headless path now also auto-detect: without an explicit -Version, they build against whichever supported version (pinned or bridge-patched) the locally installed Vintage Story actually is, matching install-linux.sh's existing auto-detection instead of requiring an exact match to the pinned default.
- docs/vintage-story-version-updates.md documents both ways to handle a new Vintage Story release going forward, in order of preference: pin real upstream source once Anego publishes it (always try this first), or bridge-patch from the compiled client as a temporary stopgap (this release's 1.22.6 support).
Fixes
- bootstrap.sh/bootstrap.ps1 no longer silently reuse a stale .vanilla/win-x64 client cached from a previous --version/-Version; the extracted client's own version marker is now checked before reuse, and a mismatch triggers a clean re-extraction.
- package-linux.sh/package-macos.sh had the same stale-cache bug in their own separate per-platform .vanilla caches, with a more serious effect: packaging for a new version after a prior version's package run silently grafted Optimum's patches onto the old version's vanilla VintagestoryLib.dll as the Cecil transplant donor, shipping a client whose engine IL didn't match the version its compiled mod-API assemblies expected. Both scripts now re-extract on a version-marker mismatch, matching the fix already applied to bootstrap.sh.
- check-vanilla-compat.sh no longer asserts a hardcoded game-version string; it now reads the actually-extracted client's own version marker.
- bootstrap.sh's upstream-bridge directory lookup is generic (patches--bridge/) instead of hardcoding 1.22.6, matching a new equivalent implementation added to bootstrap.ps1 (which previously had no bridge-patch support at all - a Windows -Version build of a bridged version would have silently built unpatched, mismatched source).
- install-windows.ps1 silently dropped the real error when the runtime donor build (VSEssentials/VSSurvivalMod) failed, showing only a generic "Runtime donor build failed" message with no compiler output to diagnose it. The install script now streams that output into the log as it happens instead of buffering it until after the (failed) call returns, so the actual dotnet build error is visible.
- Installing to a drive root (C:\Optimum, D:\Optimum) failed at the final cleanup step with "The path is not of a legal form". PowerShell 5.1's Split-Path -Parent strips the trailing backslash from drive roots, turning the parent path into a relative path that Join-Path/New-Item/Move-Item reject. Install-StagedPackage now normalizes that path back to an absolute one, with a regression test covering it.
- prepare-runtime-donors.ps1/.sh wrote the user's Vintage Story install path straight into generated XML for the VSEssentials/VSSurvivalMod runtime donors. An install path containing & (e.g. "C:\Modding & Games...") produced invalid XML, which MSBuild reported as MSB4025 "error occurred while parsing EntityName" and failed the runtime donor build. Both scripts now XML-escape &, <, > in every path written into generated project files.
- A macOS bootstrap downloaded the client archive, then failed on the next line (VintagestoryLib.dll: No such file) because the macOS tarball roots at "Vintage Story.app" instead of "vintagestory" like the Windows/Linux archives. extract_archive now normalises a single extracted root to vintagestory when that directory isn't already present. (#13)
- curl wrote the client and innounp downloads straight to their cache paths, so an interrupted download left a short file that the reuse check only asks whether exists - the next run reported "Using cached" and failed in tar/innounp instead, with nothing pointing at the download. Both downloads now write to a .partial path and move into place only on success; bootstrap.sh also verifies a cached archive reads end to end before trusting it. (#14)
Community contributions
Thanks to @dizzyd for both fixes above:
- #13 - macOS bootstrap archive-root fix
- #14 - atomic client/innounp download, fixing a poisoned-cache bug
Validation
Verified end-to-end on Linux: make bootstrap VERSION=1.22.6 && dotnet build succeeds against the real downloaded 1.22.6 client (95/95 patches applied, 0 build errors, 0 warnings). The packaged install-linux.sh output launches, reaches the login screen with no crash, and completed a real online session (server validation, joined a public multiplayer server, exchanged chat, disconnected cleanly) with no exceptions in client-debug.log or server-debug.log.
Windows and macOS builds are implemented and syntax-validated (pwsh-parsed, bash -n-checked) but not yet retested on those platforms - no Windows or macOS host was available to run them end to end this session.
1.22.5 continues to build and install exactly as before this release (VERSION defaults to forks.json's pinned value).
v0.3.4
Fixes
- Restored the missing unified-diff header in
SystemRenderOITLayers.cs.patch. - Added patch-syntax validation before bootstrap applies patches.
- The launcher exits before game startup when a required patch, assembly validation, or JIT preflight fails.
Notes
- Targets Vintage Story
1.22.5. - Source-only release. See the wiki for install instructions.
v0.3.3
Fixes
- Added guards to
SystemRenderOITLayersandGearRendererfor missing render resources, meshes, shaders, callbacks, and state. - Added null-safe player head animation setup and pose lookup.
- Added dependency closure for injected helpers, complete method signatures, IL stack validation, and strict manifest checks.
- Added cache hashes, temporary writes, path checks, locks, timeouts, and rollback handling. Failed patching stops before
Vintagestory.exestarts. - Added
Optimum.exe --validate-onlywith assembly loading, reflection, and JIT preflight. - Hardened the Windows installer with package completion checks, runtime preflight, temporary installation swaps, and rollback.
- Accepted stable four-part
ilspycmdversions from10.1.0.8386through10.1.1.8388, inclusive. The build prefers10.1.1.8388. - Unified LF normalization across Bash and PowerShell bootstrap paths.
- Added the standalone Windows uninstaller and kept the original Vintage Story installation untouched.
Notes
- Targets Vintage Story
1.22.5. - Source-only release. See the wiki for install instructions.
v0.3.2
Fixes
- Prospecting pick node search silently did nothing. Node Search Mode never printed "no ore nearby" or any quantity result (Density Search Mode still worked). Two fields added for the node-search optimization had no initializer run under the runtime patcher, so the first lookup threw before any chat message could be sent. Fixed by lazily initializing on first use.
- Animation lookups allocated a string every frame.
AnimatorBase.animsByCodeused the default ordinal comparer in the shipped API assembly, so every animated entity's animation-state lookup lowercased its animation code on every call. The source-tree fix existed but had no Cecil target and never reached players; it now ships via a dedicated API patch. - A missing Cecil type target could fall an install back to vanilla.
Optimum.OptimumOptiTimeGuardwas absent from the engine's type-injection list, so any install exercisingGetMigrationMessage()failed self-consistency verification. - The version string stuck at 0.3.0/0.3.1 across releases.
Performance (opt-in, default off)
Dressed NPCs (traders, villagers) re-tesselating their shape and gear could cost 50-230ms in a single frame, most visible when several load in at once (a caravan arriving, entering a village). Four settings target this, configurable only via ModConfig/optimum.json for now:
EntityTesselationFrameBudget: caps how many dressed-entity re-tesselations run per frame, spreading the rest across following frames.EntityOutfitShapeCache: reuses the fully assembled gear shape and resolved textures for outfit combinations already built once.EntityOutfitAnimatorCache: reuses the built animator (pose graph) across entities sharing an outfit combination instead of rebuilding it every re-tesselation - the largest remaining cost in this path.EntityOutfitTexturePrewarm: inserts every outfit variant's textures into the atlas during the loading screen instead of on first encounter mid-gameplay.
Measured via .optimum stutterwatch, together these reduced per-call cost from 58-230ms down to 7-9ms in real gameplay testing.
Diagnostics
- New
.optimum stutterwatch <ms>command attributes per-frame cost to Optimum's own subsystems, complementing vanilla's.debug logticks.
Notes
- Targets Vintage Story 1.22.5.
- Source-only release. See the wiki for install instructions.
v0.3.1
Fixes
- Runtime donor build fails during install when ILSpy generates self-closing
<Reference>elements without<HintPath>. MSBuild cannot resolve assemblies like Newtonsoft.Json from a bare<Reference Include="Newtonsoft.Json" />. AddedResolve-ProjectReferences/resolve_referencesto both the PowerShell and bash donor scripts. A two-pass strategy expands self-closing references (finds DLLs in the vanillaLib/, root, andMods/directories) and replaces invalid relative HintPaths with correct absolute paths from the user's local installation. <LangVersion>hardcoded to15.0. ILSpy changes the emitted version number across releases. The regex now matches anyX.Yversion.- macOS bash 3.2 compatibility.
set -o pipefail,sort -z, andfind -quitare GNU extensions unavailable on macOS's bundled bash and BSD find. All three now have conditional fallbacks. - PowerShell 5.1 compatibility.
$IsWindowsis PS 6+ only; the fallback path now probes for bothilspycmdandilspycmd.exe. Native build commands useInvoke-NativeStep(from_exec.ps1) to prevent stderr lines from becoming terminating errors under$ErrorActionPreference = 'Stop'(PowerShell/PowerShell#4002). - Missing reference-path directories are now reported. Vanilla
Lib/, root, andMods/directories are checked for existence before being passed toilspycmd --referencepath. Missing directories emit a warning instead of silently being skipped. - Build step tries all donors before failing. If one donor (VSEssentials or VSSurvivalMod) fails to build, the script now attempts the other and reports all failures at the end.
v0.3.0
New
- Optimum is now a launcher, not a pre-patched game. 0.2.x shipped complete optimized assemblies. 0.3.0 ships
Optimum.exe(the launcher),Optimum.Patcher.dll(Cecil), and runtime "donor" DLLs, and patches copies of the game's own assemblies at launch. Four shipping paths carry the changes: an engine Cecil transplant intoVintagestoryLib.dll(4 injected types, 52 method transplants, 1 IL hook), an ABI-safe API patch intoVintagestoryAPI.dll, runtime donor manifests forVSEssentials.dllandVSSurvivalMod.dll, and an asset overlay for the optimized shaders and the Optimum language strings. Vanilla files on disk are never modified. - Patch results are cached and validated per launch. First launch (or the first launch after a Vintage Story or Optimum update) applies the patches and writes them to
.optimum/cache/; every launch after that validates the cache and loads it. If any patch step fails, the launcher falls back to a vanilla launch instead of leaving a half-patched install. - Branded patch splash screen. The cache-miss path now shows a small window styled after the vanilla
GuiScreenLoadingGame(same font family, colour, and size), updated per patch target, so the first launch reads as one continuous loading sequence instead of a console flash. It is a core-profile GL 3.3 renderer with a logging GLFW error callback, so a headless host, a Wayland compositor rejecting window positioning, or an unsupported driver degrades to log-only and never blocks patching. - No more console window. The launcher builds as
WinExeand writes every diagnostic line to{dataPath}/Logs/optimum-launcher.log(truncated per run) instead of holding a console open for the whole session. - Optimum window identity. The client window title is
Optimum, the window icon is set from the Optimum-brandedgameicon.pngat window creation (rather than relying on WM_CLASS resolution, which OpenTK's bundled build cannot set), andGameVersion.LongGameVersioncarries a+ Optimum v0.3.0suffix on Windows, Linux and macOS. - Vintage Story 1.22.5. All patches were regenerated against the 1.22.5 baseline and the fork refs in
forks.jsonwere moved to the 1.22.5 source release.
Performance
- Map page cache.
ChunkMapLayer's per-chunk upload pipeline is replaced by an 8x8-chunk page cache (256x256 px per page). Pages persist toModData/optimum-map/{world-id}/as GZip-compressed RGBA written by a background coalescing thread, upload into aGL_TEXTURE_2D_ARRAY(128 layers, free-list allocator, LRU eviction) with BC7 Mode 6 compression whenGL_ARB_texture_compression_bptcis present (64KB vs 256KB per page), and draw through a singleGL.DrawArraysInstancedcall against theoptimum-mapshader instead of one draw per map component. 2x and 4x box-filtered LOD tiers serve zoomed-out views, and a min-heap priority queue keyed on squared distance from the viewport centre loads near pages first. Falls back to the vanilla per-component path when no pages are resident. - Optional map terrain pre-generation. With
MapPageCachePregenenabled in.optimum/optimum.json(default off), unexplored chunks entering the viewport are filled on the background map thread with biome-approximated colour derived from the world seed plus the client-side climate, ocean and forest maps, shaded by replaying GenTerra's noise (9 octaves, freq 1/3267, persistence 0.9) over a 34x34 padded height grid for NW-illuminated slope brightness. Pregen pixels are desaturated 35% so they stay visually distinct from explored terrain, and explored data always wins. - Wind and rain volume deadzone now actually ships.
WeatherSimulationSound.updateSoundsskipped OpenALSetVolumecalls below a 1% delta in the source tree since 0.2.x, butupdateSoundswas never a transplant target, so the shipped game never ran it. It is now in the Essentials runtime manifest along with its four tracking fields.
Fixes
- Native
0xc0000005crash when switching settings tabs._AddOptimumTabwidened the settings background bounds without widening the composer's own surface bounds.GuiElementDialogBackgroundblurs up toBounds.OuterWidththrough raw unclamped pointers, so a background wider than the Cairo surface overran the native pixel buffer — a stack-trace-less access violation on every in-game tab switch, previously misattributed to wideningc.Boundsand reverted twice without being fixed. Every fixed-width ancestor bounds, up to and including the composer's own, now grows by the same delta so background and surface stay in lockstep on both the main-menu and in-game paths. - A patch-resolution failure could silently drop the whole install back to vanilla. The Cecil assembly resolver for the
--modruns searched only the vanilla input directory, itsLibfolder, the donor directory and the output directory — none of which holdVintagestoryAPI.dllfor the mod pass (input comes from.optimum/vanilla/Mods, donors from.optimum/donors). It stayed invisible until Cecil had to resolve a type while writing output, at which pointAssemblyDefinition.WritethrewAssemblyResolutionException, the patcher process died, and the launcher fell back to a fully vanilla launch — discarding even assemblies that had patched cleanly. The resolver now walks up from both the vanilla and compiled paths to the first ancestor directory containingVintagestoryAPI.dlland adds it plus itsLibfolder. - Chiseled blocks cast full-cube shadows at any distance. Two separate bugs. The shadow-cull path (
CullInstantShadowPassNear/Far) had no chisel-distance awareness in either the source tree or the shipped runtime, so vanilla's plainLodLevel >= 1check let the cheap full-cube LOD 3 proxy into the depth map even right next to the camera. Separately,OptimumApiBridge.InFrustumAndRangeignored itslodLevelparameter for chisel-flagged locations and returned one shared boolean for both LOD 2 and LOD 3 — up close both meshes drew together (z-fighting), and past the chisel distance the block vanished entirely instead of falling back to the proxy. Fixed with a newInFrustumShadowPassbridge method plus aPatchChiselLodShadowHookIL hook, a sharedOptimumChiselLodVisiblehelper for the source tree, and per-lodLevelbranching inInFrustumAndRange. - A self-reference in a transplanted method broke every patch on install.
updateSoundscontains.All(s => s.IsReady)LINQ predicates that the C# compiler caches as static delegates on a generated<>cclass. Cecil member injection clones only the named method, not that nested type, so the transplanted IL referenced a method that did not exist in the output.SelfConsistencyVerifiercorrectly refused to write, but the resultingModPatcher.Patchexception was unhandled inOptimum.exeand killed the launcher mid-sequence, leavingVSEssentials.dll,VintagestoryLib.dllandVintagestoryAPI.dllall fully vanilla — no Optimum tab, no version suffix. Both.All(...)calls are now explicit loops, with a regression test. - Four crashes from Cecil-injected fields whose initializers never ran. Member injection adds a field slot but does not run constructor IL, so any C# field initializer on an injected field silently vanishes.
WeatherSystemClient.optimumWindSpeed/optimumSurfaceWindSpeed(nullVec3d, NRE on the first render frame after joining, now plain locals),AStar.optimumNodePool(NRE per pathfinding attempt, swallowed by vanilla's try/catch, leaving every pathing entity unable to move; now lazily allocated),MechanicalPowerMod.optimumTickNetworks(NRE on every server tick, ~16,000/s, producing a 73MB log and an auto-shutdown at 100,000 errors; now lazily allocated) andTreeGen.vineScratchPos/positionStack. - NRE on joining a world.
WeatherSystemClient.OnRenderFramedereferencedcapi.World.Player.Entitybefore the local player existed, and thenWeatherDataAtPlayerbefore the server'sLevelFinalizepacket had populated it. Most reproducible in singleplayer, where render frames tick faster than the integrated server's join sequence. Both are now guarded with an early return. - The Optimum settings tab disappeared, and the Back button overflowed the panel.
_AddOptimumTabwidenedc.Bounds, the dialog composer's own on-screen extent, which is a different object from what the tab buttons are parented to — so the dialog re-centred without moving the buttons, pushing the Optimum tab and Back button outside the panel background. Fixed by wideningoButtonBounds.ParentBounds(the real layout parent) on both paths, and additionallyc.Boundsin-game only. Inter-tab gaps are 10px and the Back gap 15px so the row fits narrow windows, and the Extra tab's switches are now centred on their label rows. - Crucible rendered pitch black above 900°C (#9718 regression).
CrucibleInFirepitRendereradded incandescence colour straight intoRgbaLightIn, saturating the light vector above 1.0, which some Nvidia drivers render as black. Glow now goes through the dedicatedRgbaGlowInuniform withalpha = extraGlow/255f, matching theForgeContentsRendererpath that always worked. - FSR at native resolution no longer differs from vanilla. The LOD-bias
SamplerParameter/TexParametercalls are skipped when Render Scale isNative (1.0x); vanilla never sets them, so native-resolution output is now bit-identical to vanilla instead of subtly different. - The version label was appended twice. Both
ApiPatcher.PatchGameVersionLabel(rewritingGameVersion..cctor) and a source patch inClientProgram.MainsuffixedLongGameVersion, producingv1.22.5 (Stable) + Optimum v0.3.0 + Optimum v0.3.0. The Cecil patch is now the single source of truth. - Three patches were out of sync with
build/. The settings-tab spacing fix and the FSR native LOD-bias skip were edited directly inbuild/VintagestoryLibwithout regenerating...
v0.2.11
Fixes
-
0.2.10 installs failed at the packaging phase. The 0.2.10 release shipped the FSR patches without the matching injection entries in Optimum.Patcher, so the Cecil self-consistency gate found six dangling references to
ClientSettings.OptimumRenderScaleand aborted every fresh install with "output not written". The patcher now injects the FSR members (OptimumRenderScale,onOptimumRenderScaleChanged,FsrEasu,FsrRcas,RegisterOptimumShaderProgram, texture LOD bias helpers) and transplants the six FSR method bodies. -
The Windows GUI installer ignored
-VsPath. The flag reached the headless-Silentpath but not the graphical one, so running the normal installer window with multiple Vintage Story installs present picked whatever install auto-detection found first, regardless of what was passed on the command line. The hidden path field behind the GUI now seeds from-VsPathat launch, matching the flag's documented behavior. -
install-linux.shcould hang forever during the prerequisite check on some systems. Reading ilspycmd's version by piping its live output intohead -n1could deadlock:headexits after the first line and closes its end of the pipe, and the .NET tool writing further output into a closed pipe can block instead of erroring out. The script now reads the tool's full output before parsing it, with a 5s timeout backstop, so the prerequisite check can no longer hang. -
ilspycmd install/update could fail with
Tool 'ilspycmd' is already installed.even after a hang-free run.dotnet tool update --versionrefuses to move to a version NuGet considers a downgrade, and the fallback totool installcould never succeed either since install always refuses when the tool ID already exists. The update call now passes--allow-downgrade, and the fallback uninstalls first.
Verification
- Full install pipeline (bootstrap, decompile, 57 patches, build, Cecil patch) passes with both accepted ilspycmd versions, 10.1.0.8386 and 10.1.1.8388: 50/50 methods patched, 93 members injected, zero self-reference errors, patched DLL written.
Notes
Source-only release. See the wiki for install instructions.
v0.2.10
Performance
- FSR 1.0 upscaling. EASU upscales a downsampled scene to native resolution; RCAS applies adaptive sharpening. RenderScale (25-100%) and FsrSharpness (0.0-1.0) appear in the Extra settings tab. At 75% render scale, fill-rate cost drops with no perceptible resolution loss.
Fixes
- Install crashes after a successful build. Windows PowerShell 5.1 turned routine stderr output into a build-ending error under
$ErrorActionPreference = 'Stop', including ilspycmd's own false "not using the latest version" notice and blank lines fromdotnet run. bootstrap.ps1, package.ps1, install-windows.ps1, package-linux.ps1, and package-macos.ps1 now shield every native call (ilspycmd, dotnet, git, curl, innoextract, tar, hdiutil, cmake) from this. - install-linux.sh rewrite. Interactive prerequisite installer. Detects apt, dnf, zypper, and pacman (SteamOS). Detects .NET SDK 10 even when the system dotnet reports 9.x. Walks through each missing tool before building.
- ilspycmd version range. .config/ilspycmd-compat.json now accepts 10.1.0.x and 10.1.1.x. bootstrap.sh and bootstrap.ps1 read from it; no hardcoded version.
Notes
Source-only release. See the wiki for install instructions.
v0.2.9
Fixes
- ilspycmd version nag. The pinned decompiler (10.1.0.8386) started refusing to run when a newer version exists on NuGet, printing "You are not using the latest version" and aborting. Bootstrap now accepts any 10.1.0.x or 10.1.1.x without forcing a reinstall. Pin bumped to 10.1.1.8388 (identical decompile output, verified by diff).
Notes
If you downloaded v0.2.8 and hit the "not using the latest version" error during install: this release fixes it. Re-download and re-run the installer.
Source-only release. See the wiki for install instructions.
v0.2.8
Performance
- Entity light batching. Chunk light queries for all visible entities resolve in one pass per chunk segment. 42-67% CPU reduction at 32-512 entities, zero per-frame allocation.
- Shader state cache. viewMatrix, waterWaveCounter, and the Animation UBO upload once per render pass. Each renderer reads the cached result instead of re-uploading per entity. 83% reduction at 32+ entities.
- Greedy mesh UI. The Extra settings tab shows MaxMergeWidth/Height sliders when the feature is enabled.
Fixes
- Background FPS focus debounce. 300ms hysteresis prevents false limiter triggers during world load and rapid alt-tab sequences.
- Frame pacing timer resolution.
timeBeginPeriod(1)on Windows for 1ms sleep granularity. Core-count gate (ProcessorCount > 5) falls back to Yield-only pacing on low-core machines. - VS Roofing 1.6.3 LOD. Roof blocks no longer trigger chisel LOD substitution. The gate checks the
canChiselattribute before replacing meshes. - Thunderlord animation.
GearRendererentities skip the animated block LOD gate via aSkipAnimLodflag. - Linux font rendering. Ships vanilla pristine
cairo-sharp.dllinstead of a recompiled copy. Glyph shape differences gone.
Notes
Source-only release. Clone the repo and run the installer for your platform. See the wiki for instructions.