Skip to content

Changelog

All notable changes to the OxHeart package will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[1.1.2] - 2026-08-20

Documentation

  • Third Party Notices.md corrected — it understated the packages OxHeart declares and gave the wrong import order. Addressables was listed as 2.5.0; the declared dependency is 2.9.1. Two declared built-in modules, com.unity.modules.audio and com.unity.modules.physics, were missing from the table entirely. The introduction also stated that Unity installs OxHeart's package dependencies automatically — true for a UPM install, false for the Asset Store .unitypackage, which is how you received OxHeart: Addressables and Newtonsoft JSON must be installed through Package Manager before importing, or the package will not compile. The publisher line also still carried pre-rebrand product naming. Nothing about your licence obligations changed; the file now describes the dependency set accurately.
  • Documentation~/README.md System Requirements corrected — same stale Addressables 2.5.0 claim, no uGUI entry, no mention of the built-in Audio and Physics modules, and no import-order warning. It now matches the package README and the notices file.
  • A hardcoded version was removed from the Deterministic RNG sample's README, which used .../1.1.0/Rng/ as a literal example of the imported-sample path.

Documentation only. No code, public API, serialized field, dependency, or link.xml entry changed. Migration note: none.

[1.1.1] - 2026-08-20

Fixed

  • Importing the package into a Unity 6000.6 project logged eight deprecation warnings. Unity 6000.6 ships two new compiler analyzers that the engine tripped on every compile — UAC0009 (the DEVELOPMENT_BUILD preprocessor directive is deprecated in favour of the managed-code-variant directives) and UAC0005 (AppDomain.CurrentDomain.GetAssemblies() may return assemblies Unity has already unloaded, risking errors or leaks). Warnings only — the package compiled and ran correctly on 6000.6 throughout — and nothing here changes behaviour on any Unity version. Every DEVELOPMENT_BUILD guard is now DEBUG, including the six [Conditional] attributes on OxLogger's Log/DebugLog/LogEmphasis (and GameLogger's). Unity defines DEBUG in the editor and in development player builds and leaves it undefined in release players — exactly where DEVELOPMENT_BUILD was defined — so the ADR-006 release-stripping contract documented in the Logger guide is unchanged: those call sites, argument evaluation included, still compile to nothing in a release build. The reflection scans that enumerate loaded assemblies (external IServiceRegistrar discovery, runtime service-interface discovery, and the editor's dependency checker, save-type validator and sample-scene builder) now route through a single helper that calls Unity's CurrentAssemblies.GetLoadedAssemblies() on 6000.6+ and keeps the previous AppDomain call, unchanged, on 6000.0–6000.5 where that API does not exist — the declared 6000.0 floor is untouched. Migration note: none — but if your own game code guards debug-only paths with #if DEVELOPMENT_BUILD, you will see the same UAC0009 warning on 6000.6; the fix is the same swap to #if DEBUG, or Debug.isDebugBuild for a runtime check.

Documentation

  • Corrected the link.xml placement guidance, which promised protection on an install path that would not have had it. The bundled link.xml comment and the IL2CPP guide both stated that Unity honours a link.xml at a UPM package root. It does not — package-root link files are intentionally ignored by the linker, and the supported mechanisms for a UPM package are an assembly-embedded link.xml or IUnityLinkerProcessor.GenerateAdditionalLinkXmlFile. Your project is unaffected in OxHeart's shipped form: the Asset Store .unitypackage restores to Assets/OxHeart/, and Unity honours a link.xml anywhere under Assets/ — which is the mechanism the file has always actually relied on. What was wrong was the promise made for a different install path: the Quick Start also documented an "Add package from disk / git URL" route, and on that route the bundled file would have been silently ignored while the IL2CPP guide said engine types were "safe at any stripping level with no action on your part" — the linker would then have stripped the engine's reflection-created service constructors, surfacing at boot as Cannot register null service of type I… in an IL2CPP player only, with no compile error and no Editor symptom. The Quick Start no longer documents that route (.unitypackage import is the supported install), and both the comment and the guide now state the real rule plus what to do if you relocate the folder into Packages/ yourself: copy the four <assembly> entries into a link.xml under your own Assets/. Documentation and one XML comment only — no code, public API, serialized field, or <assembly> entry changed. Migration note: none.
  • Dropped stale "in 1.0" phrasing from the cheat sheet, config guide, docs README and Time Management page, and removed the 1.0.x sections from this changelog. Those versions were internal pre-release checkpoints that were never published, so they described releases no buyer received.

[1.1.0] - 2026-08-14

Added

  • Tools > OxHeart > Validate Save Types — catches save fields IL2CPP stripping would silently drop. Scans every PersistableData subclass in your project and reports public auto-properties, the one save-data shape the managed linker can remove without any runtime signal. If nothing in your own code ever calls such a getter — ordinary for data written during play and read back only by the loader — the linker strips it, the serializer never sees the member, and its key is simply absent from the save file: no exception, no warning, no quarantine entry. The report names each type and property and gives the two fixes that work (a [SerializeField] backing field, or a link.xml entry), including the caveat that changing shape renames the serialized key and so needs a SchemaVersion bump plus a migration. This is deliberately an editor check: after stripping, reflection has no baseline to compare against, so a player build cannot detect what it lost — the shape has to be caught before the linker runs. Types in the engine's own assemblies are skipped, since the package-root link.xml already preserves them. Editor tooling only — no runtime service, no ServiceConfiguration toggle, no runtime cost. Migration note: none — additive.
  • Scene Management service (ISceneService). A new optional, opt-in service (off by default — enable "Scene Service" in ServiceConfiguration, under Game Services) for async scene load/unload with progress. LoadSceneAsync(nameOrKey, options) streams a scene from the Build Settings list or Addressables — one SceneSource.Auto option resolves Addressables-then-build-settings and falls back quietly with no error-log noise when Addressables has no entry for the key (the P1 quiet-fallback contract). Progress is reported as a monotonic 0 → 1 (Unity's allowSceneActivation 0.9 activation quirk is mapped) through both a direct OnLoadProgress event and pooled SceneEvents.LoadStarted/LoadProgress/LoadCompleted bus events. Optional CanvasGroup fade-out/fade-in is driven by the service's own DontDestroyOnLoad overlay canvas — it never touches your camera or game UI — and an optional AutoSaveBeforeLoad requests an immediate save through the AutoSave service before the load (warn-and-skip if AutoSave is disabled). The service is graceful: a missing scene, an empty key, or a load superseded by a newer one returns a failed SceneLoadResult and never throws; a second load cancels the first (single in-flight load, no queue — deliberately no scene-dependency graph). SceneServiceConfig supplies the fade colour, opt-in fade durations, and overlay sort order. Ships with the Scene Management sample (Samples/SceneManagement/): SceneTransitionSample + SceneTransitionA/SceneTransitionB scenes demonstrating Auto / Build Settings / Addressables loads with fades and an on-screen progress bar. See the "Scene Management" documentation and ADR-024. Migration note: none — additive and off by default.
  • Game Settings service (IGameSettingsService). A new optional, opt-in service (off by default — enable "Game Settings Service" in ServiceConfiguration) that owns the standard graphics/display options — quality tier, resolution + fullscreen mode, vsync, target frame rate — persisting them across restarts (PlayerPrefs), applying them on init, and raising SettingsChanged. Resolution changes go through a revert/confirm safety: ApplyResolution(...) applies the new mode immediately and returns an IResolutionChangeHandle; unless you Confirm() it before the timeout it auto-reverts to the last confirmed resolution. The countdown runs on the Time service's unscaled clock, so it still fires while the game is paused (timeScale = 0), and an unconfirmed change is reverted on the next launch — so a bad mode can never strand the player, even across a crash or quit mid-countdown. Per-setting "manage" toggles on the new GameSettingsConfig let a game hand only some settings to the service. Requires the Time service (on by default). Ships with a click-and-run GameSettings.unity scene wrapping the existing runtime-built options panel — the "Screen now" readout reports the actual measured frame rate (a Time.unscaledDeltaTime-based counter, averaged and refreshed twice a second) alongside the Target FPS setting, so stepping the target visibly caps the real rate rather than only showing the value you chose. CurrentResolution tracks the resolution the service itself last applied rather than reading Screen.currentResolution back — in windowed / FullScreenWindow (the default fullscreen mode), that Unity API reports the display's native mode and never reflects a Screen.SetResolution call, which made a confirmed resolution change look like it silently failed to persist even though it had genuinely saved and was correctly re-applied on the next launch. CurrentResolution still falls back to the raw display read when resolution is unmanaged. See the "Game Settings" documentation and ADR-023. Migration note: none — additive and off by default.
  • Default notification toast presenter (NotificationPresenter). A drop-in, sample-adjacent MonoBehaviour that renders INotificationService notifications as stacked, priority-coloured toasts — so buyers see notifications on screen without building UI first. Add it to any GameObject in an initialized scene (Notification + Event services enabled) and it builds its own uGUI canvas at runtime; no prefab or scene wiring required. It is a pure view: the notification service owns expiry/dismissal (and already never auto-dismisses Critical or persistent notifications), so the presenter runs no timers of its own — it adds a toast on NotificationSent and removes it on NotificationExpired/NotificationDismissed, honouring stacking, a configurable MaxVisibleToasts, priority ordering, and persistence. It lives in the OxHeart-Samples assembly; the engine core gains no dependency on it, and the NotificationSystem sample now spawns it automatically. Migration note: none — additive.
  • Third-Party Notices generator (editor tooling) + display sample. New editor tools under Tools > OxHeart > Legal/ that scan resolved UPM packages, read each package's license file (LICENSE.md / LICENSE.txt / LICENSE / Third Party Notices.md), merge them with a buyer-curated text file, and write a consolidated THIRD_PARTY_NOTICES.txt (default output: Assets/StreamingAssets/THIRD_PARTY_NOTICES.txt). A committed ThirdPartyNoticesManifest ScriptableObject holds paths, product name, exclude / force-include lists, and two opt-in build flags (RegenerateOnBuild, ValidateOnBuild — both off by default so the engine is never hostile-by-default). The Package Manifest window exposes one checkbox per package whose state mirrors the generator's include decision. This is editor tooling only (like the Setup Wizard) — no runtime service, no ServiceConfiguration toggle. Ships with the Third-Party Notices uGUI sample (Samples/ThirdPartyNotices/) that loads the generated file from StreamingAssets cross-platform and displays it in a scroll view, plus a click-and-run ThirdPartyNotices.unity scene (camera + component; no engine initializer). Docs carry an explicit "convenience tool, not legal advice — you own your attribution obligations" disclaimer. See the Third-Party Notices documentation. Migration note: none — additive.

Fixed

  • The Data Persistence sample silently saved 0 when a numeric field held non-numeric text. Typing something like abc into Level or Gold fell through a TryParse-or-zero helper, so the save reported success while the value read 0 — indistinguishable from the save system having dropped it. The sample now says so: the status line names the offending field and its rejected text, and a warning goes to the console. 0 is still stored so Save never blocks, and an empty field stays a quiet 0 (that is the ordinary "nothing typed yet" case, not a mistake). No engine change — the save system stored exactly the value it was handed; only the sample's input handling was silent about coercing it.
  • The Scene Management sample gave no hint that an Addressable scene's address must match the key. Ticking Addressable on a scene makes Unity default its address to the full asset path (Assets/…/Scenes/SceneTransitionA.unity), but LoadSceneAsync is called with the key SceneTransitionA — so the Addressables load path failed with nothing explaining why. ISceneService itself now returns an actionable error instead of a bare "not found": a failed SceneSource.Addressables load names the exact address the scene needs and warns that Unity's default address (the full asset path) will never match, and a failed SceneSource.BuildSettings load points at File > Build Settings. The sample's own failure message names the address too, and the Scene Management guide states the rule for your own scenes — including the wrinkle that SceneSource.Auto hides the mismatch by design (its Addressables probe is silent, so a wrongly-addressed scene quietly falls back to Build Settings and looks like it worked). Diagnostic text only — SceneLoadResult.Success, the failure conditions, and every API signature are unchanged.
  • ClearNotifications() left cleared notifications on screen forever. The method emptied the active set directly, so it raised no event at all — unlike DismissNotification() (which raises NotificationDismissed) and the auto-expire path (which raises NotificationExpired). Worse, clearing also suppressed the already-scheduled NotificationExpired: that path is guarded by an internal "was it still active?" check, which now found nothing to remove, so the event never fired either. Any UI driven by the documented notification events — including the bundled NotificationPresenter toast presenter new in this release — was therefore never told the notifications had gone, and kept displaying them permanently, for every notification regardless of duration. Reproducible in the Notification System sample: pressing Clear All emptied the service but left every toast on screen. A bulk clear is a manual removal, so it now raises one NotificationDismissed per cleared notification, exactly as a single dismiss does; the Dismissed-vs-Expired distinction that lets consumers tell a user action from a timeout is unchanged. Routing the clear through the shared removal helper also cancels each notification's pending auto-dismiss task, which the previous implementation left scheduled to fire and quietly no-op. Migration note: none — if you subscribe to NotificationDismissed you will now receive one event per notification when ClearNotifications() is called, which is the behaviour the event contract always implied.
  • The declared Addressables dependency floor (2.5.0) does not compile on Unity 6000.5+. Confirmed by importing the package into a clean project on Unity 6000.5.8f1 with com.unity.addressables at the declared 2.5.0 floor: compilation failed with error CS0619: 'Object.GetInstanceID()' is obsolete: 'Use GetEntityId instead.' inside Addressables' own source (AsyncOperationBase.cs, VirtualAssetBundle.cs). Unity 6000.5 hard-obsoletes Object.GetInstanceID() (an error, not a warning), and Addressables 2.5.0 still calls it — a genuine compile-time incompatibility baked into that package version, not a soft recommendation. 2.5.0 continues to compile fine on Unity 6000.0.81f1; 2.9.1 was confirmed clean on both 6000.0.81f1 and 6000.5.8f1 in the same clean-project test. The floor in package.json is now 2.9.1. Every EditMode/PlayMode/IL2CPP run in this repository has in fact compiled against 2.9.1 all along — the shipped dependency declaration was the only place still advertising the older, now-broken floor. Migration note: none — a strictly higher dependency floor, and no buyers have imported the incorrect one.
  • Every scene logged red Addressables console errors on boot in a fresh buyer project. The shipped DefaultResourceManagementConfig.asset — which every sample and every fresh consuming project resolves ResourceManagementConfig to by convention, since ServiceConfiguration.resourceManagementConfigName is blank everywhere — carried enableAddressables: 1. Any project that had not built Addressables groups (the default out-of-box state for anyone evaluating the free samples) got Addressables.InitializeAsync() called anyway, and Addressables logs its own "Unable to load runtime data" errors via internal Debug.LogError/LogWarningFormat calls as a side effect of running the operation — before the engine's own try/catch around the awaited handle ever gets control back, so no amount of exception handling in ResourceService could suppress it. Confirmed live in a clean buyer-simulation project: a built player was console-clean with the flag off and produced three Addressables errors on every affected scene with it on. enableAddressables now ships false on the default config. All 13 samples load their assets from Resources and were unaffected by the change. ResourceManagementConfig.enableAddressables's C# field default is unchanged (true) — that default is the intentional ADR-001 fallback used only when no config is registered at all — this fix is to the shipped asset's serialized value, not the class default. Flip it on once you have built Addressables groups for your project. Migration note: none — 1.1.0 is the debut, and any project that already built Addressables groups and wants the shipped default to demonstrate them can flip the one checkbox back on.
  • Sample scenes required the optional Input System package, breaking 8 of the 13 samples without it. Every EventSystem-bearing sample scene serialized an InputSystemUIInputModule, because the scene generator attached whichever input module existed on the machine that authored the scene — and that machine had com.unity.inputsystem installed. In a project without the package the component resolved to a missing script, leaving the EventSystem with no input module at all, so the on-screen buttons in Getting Started, Audio, Save/Load, Events, Localization, Notifications, Object Pooling and RNG did nothing. This contradicted the README's promise that with neither input backend available the samples "still compile, run, and work from their on-screen UI buttons" — the one configuration it explicitly guaranteed was the one that broke. The scenes now carry SampleEventSystemInstaller, which adds the right module at runtime: InputSystemUIInputModule when the package is genuinely installed and the new backend is active, StandaloneInputModule otherwise, and a clear warning when neither backend is usable. com.unity.inputsystem remains undeclared in package.json — the engine still never reads input. Migration note: none.
  • A registrar-name conflict between two IServiceRegistrar implementations sharing a type name was undiagnosable. 09_GameServiceRegistrarSample.cs explicitly recommends copying its GameServiceRegistrar pattern into your own game code — if that copy keeps the sample's original class name (an easy copy-first-rename-later slip) and the bundled sample stays compiled too, ConfigurableServiceInitializer's reflection-based registrar scan finds and runs both, and the second one's registrations collide with the first's. The resulting log, Service conflict: ... already registered by 'GameServiceRegistrar', now being overwritten by 'GameServiceRegistrar', named both sides identically, giving no way to tell that two different assemblies — not one buggy registrar calling itself — were involved. The conflict log now includes each registrar's assembly ('GameServiceRegistrar [YourGame]' vs 'GameServiceRegistrar [OxHeart-Samples]'), so the actual cause is visible immediately. Diagnostic-only change — no behavior, timing, or registration outcome is different. Migration note: none.
  • BasicAudio sample: music stopped responding to mute/volume, and couldn't be restarted, after playing for a few seconds. PlayerController.PlayMusicSound() played the bundled Demo_Music clip (8s) without loop: true, so it played once and completed naturally during ordinary testing — well within the time it takes to toggle mute and drag the volume slider. AudioService correctly disposes a completed one-shot's handle per its documented lifecycle (ADR-004), but the sample's _musicHandle field was never cleared, only IsValid flipped to false: ApplyVolumeToMusic() silently no-op'd on the stale handle (mute/volume appeared dead) and PlayMusicSound()'s dedup guard (_musicHandle != null) permanently blocked restarting. Fixed by playing the music bed with loop: true (matching its intended "keeps playing until stopped" role) and changing the guard to _musicHandle != null && _musicHandle.IsValid, so the sample recovers even if a future clip swap makes it non-looping again. Sample-only change — no engine API affected. Migration note: none.
  • Saving a Vector3 (or Quaternion, Color, Rect, Bounds, …) threw instead of saving. Storing any Unity math struct in a PersistableData failed with JsonSerializationException: Self referencing loop detected for property 'normalized'. The cause: Vector3.normalized returns a Vector3, which has its own normalized, so a general-purpose serializer reflecting over properties never terminates. The 08_DataPersistenceSample sample stored a Vector3 position and hit this, and no test covered it. JsonDataSerializer now registers converters for Vector2/Vector3/Vector4, Vector2Int/Vector3Int, Quaternion, Color, Color32, Rect and Bounds, each writing only the type's real components — which terminates the recursion and keeps payloads small (a Vector3 is three numbers, not the dozen derived members reflection would otherwise walk). Deserialization tolerates missing components (they read as zero) and ignores unknown ones, so a save written by an older or newer build still loads. Migration note: none — the previous behaviour was an exception, so no existing save file can contain one of these values. The written shapes ({"x":…,"y":…,"z":…}) are now part of the save format and are frozen under the same rule as ModelId strings and the checksum scheme.

Changed

  • Removed: the Asset Updater / asset-versioning subsystem. AssetUpdaterService, IAssetUpdaterService, VersionCatalog, SemanticVersion, AssetVersionInfo, the resource-service versioning extensions, the enableAssetUpdaterService toggle on ServiceConfiguration, the three Asset Updater fields on ResourceManagementConfig (remoteCatalogUrl, localCatalogFileName, assetDownloadSubPath), and the VersionedAssetsExample sample have all been deleted. The subsystem could not do what it advertised: the three config fields were never read (the initializer hardcoded an empty catalog URL and fixed persistentDataPath locations), and — more fundamentally — wiring them would not have helped, because downloaded files landed in a directory no resource loader ever consults, while the versioning extensions synthesized assetId_version keys and passed them through the ordinary Resources/Addressables/StreamingAssets loaders. There was no path from downloaded bytes to a loadable asset. The bundled sample also resolved a concrete type that was never registered, and the remote catalog's JSON schema was documented nowhere. Rather than ship a service whose core promise cannot be met, it is removed. Teams needing remote content today should use Addressables' own remote catalog support directly. No compatibility stubs or [Obsolete] shims were left behind — they would preserve exactly the dead surface this removal exists to eliminate. Migration note: none — this subsystem never reached a buyer. It was disabled by default, and no release of this package was ever public (see ADR-026); 1.1.0 is the debut. If you enabled the toggle while evaluating a pre-release build, remove your IAssetUpdaterService usages — nothing else in the engine referenced it, and every other ResourceManagementConfig and ServiceConfiguration setting is unchanged.
  • The engine is now OxHeart. The package has been renamed from its previous identity throughout: package id (games.moomoo.oxheart), display name, namespace root (OxHeart.*), assembly definitions (OxHeart-Core, -Runtime, -Helpers, -Utility, -Editor, -Samples), every public type that carried the old product name (OxHeartConfig, IOxHeartService, ...), the compile define symbols (OXHEART_DEV, OXHEART_TESTS_ENABLED, OXHEART_STARTUP_DIAGNOSTICS), the editor menu roots (Tools > OxHeart, Assets → Create → OxHeart, Add Component → OxHeart), the documentation site, and the repository and support URLs in package.json. The logger keeps the short form OxLogger. The brand is spelled OxHeart everywhere — internal capital, never Oxheart.
  • Nothing in the save contract moved. No save file format, ModelId, checksum scheme, or quarantine reason code (JSN / CHK / SCH, including the -BAK variants) changed. Five string literals deliberately keep their pre-rename spelling because they are save-format contract rather than branding: the package-default obfuscation salt and its companion secret in SecureKeyProvider, the AES key-derivation suffix in AesEncryptionService, the fallback XOR key in XorObfuscationService, and the PlayerPrefs key in GameSettingsService. The first four feed SHA256 key derivation — renaming them would produce a valid-looking wrong key and fail silently, so they are frozen, marked in source, and pinned by a regression suite replaying key vectors captured before the rename. See ADR-025.
  • Migration note: none. No version of this package was ever publicly released — 1.0.0 and every 1.0.x tag after it were internal checkpoints. 1.1.0 is the debut release, so there is no upgrade path from an older name and no deprecation shims for it. This is why a rename ships in a minor rather than a major; see ADR-026. From 1.1.0 onward the normal SemVer contract applies in full.
  • Sample save types now use [SerializeField] backing fields instead of auto-properties. DemoProfileData (DataPersistence sample) and PlayerData (08_DataPersistenceSample) changed shape so the samples model the pattern that is safe under IL2CPP managed stripping — see the IL2CPP entry under Documentation. Public API is unchanged: the same PlayerName/Level/Gold/Position/PlayTime properties exist with the same types and accessibility, so sample code and anything copied from it compiles and behaves identically. Only the serialized key names inside the samples' own save files change (Gold_gold), which affects nothing but a sample save written by a pre-release build. Migration note: none.
  • Sample asset renamed: Samples/TestConfig.assetSamples/SampleOxHeartConfig.asset. It is an OxHeartConfig sample asset, not a test artifact, and the old name read as one inside a shipped package. The asset GUID is unchanged, so any reference to it keeps resolving. Migration note: none — nothing in the package referenced this asset by GUID or by name, and 1.1.0 is the debut release.
  • Release tooling: releases are now cut from persistent release/vX.Y.x line branches, never from develop. Scripts/create-release.sh creates the line on the first cut of a minor; from then on every rc, final, and patch of that minor is a version-bump commit + annotated tag on the line. main is merged only for a final (non-rc) cut of the newest line, and the line back-merges to develop only when it is the newest line — patches to older lines touch neither (their fixes originate on develop and reach the line via Scripts/backport-commit.sh). New guards refuse malformed versions, existing tags, dirty trees on branch switches, and behind-origin branches. A new interactive Scripts/release-wizard.sh shows the live pipeline state (git, Asset Store review status via _internal/RELEASE_STATE.json, docs site) with a suggested next step, and drives cuts, store-submission prep, verdict recording, and docs publishing. Engine-internal only — no package code, public API, or asset changes. Migration note: none for buyers; maintainers cut from line branches from now on (see _internal/Documentation/RELEASE_PIPELINE.md).
  • Release tooling: backport-commit.sh no longer offers its inline patch release; publish-theme-only.sh speaks the versioned publisher. The backport script's "create patch release?" path bumped only package.json (skipping OxHeartVersion.cs, CHANGELOG.md, and the README badges — a tag that fails the release checklist's own version sweep); it now ends by pointing at create-release.sh for the cut. publish-theme-only.sh derives the docs minor from its frozen snapshot and passes it to the now-versioned publish-docs-local.sh (preserving the site's latest alias), instead of calling it with the removed argument-less signature. Maintainer tooling only — no package code changes.

Documentation

  • IL2CPP guide: corrected the save-type stripping advice, which was insufficient. The guide told you to add [Preserve] to a PersistableData subclass that is only constructed by deserialization. That is true as far as it goes, but it misses the case that actually causes damage: stripping applies to members, not only whole types. A public auto-property whose getter is never called anywhere in your own code — ordinary for save data written during play and read back only by the loader — can be removed by the linker even though its class survives. Newtonsoft then never sees the member and the key is silently absent from the save file: no exception, no warning, no quarantine entry. Verified on Unity 6000.0 / IL2CPP / Managed Stripping High with the data class in a game assembly: [Preserve] on the class does not rescue it — the object still serializes, minus that property. What does work, and is now documented with a comparison table: a [SerializeField] private backing field (the recommended shape — the engine's contract resolver picks these up on purpose, including private fields declared on base classes) or an explicit link.xml entry for the type. Also notes that switching an existing auto-property to a backing field changes the serialized key (Coins_coins) and therefore needs a SchemaVersion bump and a migration step. No code changed — public fields, [SerializeField] fields, and auto-properties your code does read were already safe, and Newtonsoft itself was never at risk.
  • The hosted docs site is now versioned per minor. The site publishes one folder per minor (/1.0/, /1.1/, ...) with a root versions.json driving Material's header version selector (extra.version.provider: mike in mkdocs.yml), and the root URL redirects to the latest published version. Tools/publish-docs-local.sh now takes the docs version (<X.Y> [--latest]), stages into that version's folder only, and refuses a version that does not match the tree's package.json — so docs builds stay anchored to the released tag. Publishing (commit + push of gh-pages) remains a manual, owner-reviewed step. Pages built before this change (the /1.0/ content) do not show the version selector; new versions do.

1.1.0 is the first public release of OxHeart on the Unity Asset Store. Earlier 1.0.x versions were internal pre-release checkpoints and were never published, so no changelog history precedes 1.1.0.