CHANGELOG - Ambiguous-Interactive/unity-helpers GitHub Wiki

Changelog

All notable changes to Unity Helpers will be documented in this file.

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

[Unreleased]

Added

  • Add public ObjectsTypeTraits<T> with cached value-type, exact-object, and Unity-object classifications for generic code (guide, #976).
  • Add opt-in WALLSTOP_PROTO_ONLY builds that omit protobuf-net contracts and fallback, plus caller-owned WProto buffer writers for pooled equality and hashing (#937).
  • Add value-returning WProtoFacade serialize and read methods that reject unsupported types and report typed serialization failures (#937).
  • Add WPROTO050 to report generated deserialization constructors whose base has no accessible zero-argument constructor (#937).
  • Add WProtoMember.IsPacked = false to write unpacked numeric and enum collections matching protobuf-net defaults, while preserving existing packed writes and both read forms (#937).
  • Add SystemArrayPool<T>.TryWithBuffer for synchronous borrowed spans with explicit state, optional results, and reported callback or cleanup errors (#924).
  • Add WUH022 to report boxing disposable structs and suggest concrete or constrained generic disposal (#924).
  • Add opt-in WUH021 to flag repeated size observations, including interface Count, known Length/LongLength properties, and non-predicate LINQ Count()/LongCount(), when one snapshot is intended.
  • Add WUH020 to suggest non-throwing queue and stack reads when compatible Try methods are available (#912).
  • Add explicit scalar default omission for WallstopProto consumer migrations, with ordinary consumer migration coverage and runtime-model compatibility guidance (#343).
  • Add prompt-free Sentinel workspace APIs for authored rules, preferences, profiles, build gates, and frame budgets, with isolated inputs, blank-path rejection, diagnostics, and persisted undo/redo (#901).
  • Add Mantel-Haenszel pooled odds ratios and chi-square upper-tail probabilities, including zero-cell boundaries and extreme-tail evaluation (#742).
  • Add informational WPROTO048 migration guidance for protobuf-net runtime model registrations, which do not configure generated formatters (#343).
  • Add WUH013 coverage for custom collections with public struct enumerators and immediately cached full-sequence bounds, while retaining indexed traversal for mutation and dictionary keys (#900).
  • Add a compact UI Toolkit analyzer policy list with search, syntax-highlighted code examples, per-analyzer severity controls, automatic ruleset synchronization, and a Project Settings shortcut (#883).
  • Add protobuf support for Vector4, Matrix4x4, Keyframe and BoneWeight as roots and members on both serialization routes, including IL2CPP (#343).
  • Add opt-in WUH019 to suggest caching stable loop bounds while keeping live bounds for changing collections (#894).
  • Add cached read-only enum values and an ordinal enum-name comparer for allocation-free iteration and warmed name ordering (#889).

Fixed

  • Fix effect removal callbacks losing reapplied modifiers or repeating cleanup. Stop periodic damage after an attribute callback cancels its effect (#977).
  • Fix loading caches leaking disposable factory results when weight or lifetime callbacks throw, while preserving references retained by other entries (#972).
  • Fix first-save initialization so failed writes leave the save path free for retry and competing creators keep their complete files (#964).
  • Fix WallstopProto map entries losing earlier members when one entry repeats its message value; separate entries for the same key still replace the value. Reject malformed fragments across message boundaries (#970).
  • Fix weighted caches losing entries when storage fills, exceeding their budget on replacement or overflow, and changing existing values after a failed weight calculation (#969).
  • Fix completed ValueTask coroutine adapters retaining pooled source resources when no result callback is supplied; completed cancellation is now propagated in those paths (#967).
  • Fix invalid background-worker idle timeouts stranding queued save jobs; supported fractional and infinite waits retain their behavior (#965).
  • Fix extra allocations during WProto polymorphic list reads by reusing unseeded and replacement lists; preserve constructor list identity and append order (#937).
  • Fix scene retrieval in packed players and already loaded nonactive scenes; borrowed scenes stay loaded, while owned scene disposal waits for loading and releases callbacks (#963).
  • Fix queued dispatcher tasks hanging after destruction. Cancel abandoned work, reject uninitialized or destroyed dispatchers, and preserve running task completion (#961).
  • Fix dispatcher scope cleanup attempting to destroy loaded prefab assets; scene instances still receive cleanup (#827).
  • Fix editor imports in projects without Unity Test Framework; test reporting tools activate when the framework is installed (#950).
  • Fix legacy protobuf maps losing present nullable defaults, while preserving null values and historical nonnullable bytes (#953).
  • Fix deque and cyclic buffer serialization retaining pooled scratch buffers after failed writes, including nested serialization of the same instance (#952).
  • Fix package-installed player builds losing private serialization fields and callbacks to code stripping (#950).
  • Fix cached enum display labels to use the declared member name when an InspectorName contains only whitespace; nonblank labels retain their exact text (#867).
  • Fix legacy dictionary comparisons and nullable fields; preserve runtime map, wrapper, surrogate, enum and external-provider behavior without dynamic generic construction. Reject indexers before tuple classification (#950).
  • Fix direct texture settings API calls to ignore blank platform override names while preserving other settings (#867).
  • Fix blank key labels in duplicate dictionary warnings for Unity objects with whitespace-only names; object references and literal string keys stay unchanged (#867).
  • Fix DurableFile replacements to preserve existing saves and report failure when atomic replacement is unsupported (#946).
  • Fix blank custom dropdown labels and popup titles while preserving literal string options (#867).
  • Fix concurrent thread pool disposal returning before running work and handle cleanup finish (#827).
  • Fix SINGLE_THREADED pool purges when callbacks rent, dispose or purge the same pool; selected entries receive cleanup once without stale list indices (#827).
  • Fix singleton asset recreation after stale GUID cleanup by requesting synchronous cleanup and pausing batching during creation and import (#942).
  • Fix unexpected singleton asset creation failures blocking later attempts until a domain reload (#936).
  • Stop WallstopProto WPROTO044 inheritance diagnostics at an intermediate [WProtoNotSerialized] boundary, including referenced assemblies, while retaining contradictory declaration errors (#937).
  • Fix WallstopProto map keys to match protobuf-net 3.2.56: write zero enum keys and omit zero floating-point keys while preserving reads of older payloads (#937).
  • Fix periodic effects repeating ticks before their cadence at large clocks; initial delays, tick limits, refresh phase, and catch-up limits are preserved (#934).
  • Fix cache and effect lifetimes at large clocks, including small timing offsets and jitter; preserve remaining effect time and disabled cache expiry (#932).
  • Fix discarded worker jobs retaining captured references after shutdown, including submissions racing disposal (#844).
  • Fix pool acquisition and warm-up failures leaking items or leaving phantom active rentals; acquisition callbacks can query pool state across threads (#827).
  • Fix timed cache refresh failures changing cached values or consuming initial jitter, and preserve expiry when finite lifetime plus jitter exceeds float range (#817).
  • Fix singleton loading to use the Resources root when configured subfolders contain only slashes and whitespace; spaces inside valid folders are preserved (#867).
  • Fix invisible whitespace-only toggle captions by showing (Unnamed); literal string values and padded captions stay unchanged (#867).
  • Fix sprite sheet automatic config lookup throwing for invalid paths; invalid paths and missing sidecars preserve loaded settings (#929).
  • Fix sprite sheet configuration save/load accepting blank texture paths; valid filenames containing spaces and serialized hashes keep their exact values (#867).
  • Fix blank custom validation messages hiding the default warning in standard and Odin inspectors; nonblank messages and serialized string values retain their exact text (#867).
  • Fix blank prefixes in inline editor headers when a field label contains only whitespace (#867).
  • Fix dropdowns showing invisible labels for Unity objects with whitespace-only names; object names, references, and literal string options stay unchanged (#867).
  • Fix IntMap lookups for keys sharing low bits, keep small live windows at their starting capacity during churn, and replace existing values without rebuilding the table (#926).
  • Fix main-thread guard diagnostics to omit blank context labels and use the source filename for blank member names; nonblank labels retain their exact text (#867).
  • Fix blank serialized member names creating synthetic backing fields, and reject backing-field names with blank property names (#867).
  • Fix newly constructed serialization failure diagnostics with blank reasons so they show operation failed; nonblank reasons and literal input descriptors keep their exact text (#867).
  • Fix async durable writes and copies publishing after cancellation during staging; the previous file stays intact when cancellation is observed before publication (#827).
  • Fix Prefab Checker JSON exports to include scanned folders and findings, with empty collections and escaped text (#870).
  • Fix WShowIf condition names accepting whitespace-only identifiers; literal whitespace comparison values keep their meaning (#867).
  • Fix Unity Method Analyzer recovery from corrupt compiler snapshots while preserving saved report compatibility (#870).
  • Fix test run summary ownership so blank tokens cannot finish or discard running markers; valid tokens retain their literal spaces (#867).
  • Fix subtype manifest ownership lookup accepting whitespace-only directories; valid folder names containing spaces remain unchanged (#867).
  • Fix relational component assigner and injection constructors being stripped from High-stripping IL2CPP players using Zenject, VContainer, or Reflex (#916).
  • Fix WButton coroutines retaining resources when cancelled, completed, or faulted. Nested callback updates also leave each routine intact (#911).
  • Fix persistent runtime singletons losing their authored world placement when detached from transformed parents (#644).
  • Fix PartitionPooled retaining outstanding lists when the source throws during disposal. Cleanup no longer replaces an exception from batch processing (#827).
  • Fix TrackedObjectPool callback cleanup so disposed pools retain no returned items, nested takes keep distinct ownership, and callback failures do not interrupt cleanup (#781).
  • Fix FileHelper.InitializePath accepting whitespace-only paths on some platforms. See File Operations (#867).
  • Fix percentile interpolation across extreme finite values and preserve infinite endpoints without boxing primitive elements (#742).
  • Fix command-line helpers so blank option names return no values before reading process arguments; padded names and literal values retain exact matching (#867).
  • Fix blank scene queries and temporary scene loads, and reject blank external package identifiers when resolving asset paths (#867).
  • Fix dropdown providers so blank method names return no options before method discovery (#867).
  • Fix blank animation configuration folders and sprite profile asset paths so they stop before file access or profile matching (#867).
  • Fix blank multi-file selector paths and saved directories, reject blank sprite extension filters, and prevent test run summaries from claiming blank paths (#867).
  • Fix the Pool Purging settings foldout so hysteresis and spike threshold stay with the other global options (#883).
  • Fix disposal lease slot growth when a lease is acquired on one thread and disposed on another (#888).
  • Fix sprite pixel helpers for readable compressed textures and avoid reading a full atlas when extracting one sprite (#886).
  • Fix UI pointer helpers so hits on other objects and invalid canvas raycast positions do not return points off the target rectangle (#884).
  • Fix editor test-runner detection so project paths and log filenames containing test flag names do not suppress confirmation dialogs (#885).
  • Fix SemaphoreLease so a stray permit release raises SemaphoreFullException when its lease is disposed (#887).

Improved

  • Reduce repeated pool purge and disposal snapshot allocations with bounded buffer reuse that preserves nested callback ownership.
  • Improve directly constructed PooledResource<T> wrappers with no return callback by avoiding disposal tracking; callback-backed leases remain copy-safe (#924).
  • Improve large Deque<T> array transfers with contiguous copies, preserving logical order, offsets, and covariant destination behavior (#904).
  • Improve JesseSort for mixed regions, sparse disorder, and natural runs using the upstream live-phase design (#747).
  • Improve parent and child collection transfers with bulk copying for Component fields while retaining typed enumeration for derived components (#781).

Removed

  • Remove production test-only APIs and injected callbacks; package tests now access real implementation internals (#935).

[3.6.2] - 2026-09-28

Added

  • Add SpriteSettingsApplierAPI.TryApplyProfiles to batch-apply and save sprite importer profiles from scripts without opening the window (#821).
  • Add TextureSettingsApplierAPI.TryApplyTextureSettings to apply and save texture import settings from scripts without opening the window (#821).
  • Add AnalyzerPolicyAPI.TrySetEnabled so editor scripts can enable or disable Unity Helpers analyzer policies without opening the window (#821).
  • Add ScriptableSpriteAtlasGenerator.TryCreateConfig and TrySetSourceTexturesUncompressed to create atlas configs and manage source sprite import settings without opening the window (#821).
  • Add DisposableScope for allocation-free using cleanup with copy-safe, reverse-order disposal (#875).
  • Add SpriteSettingsApplierAPI.TrySaveProfiles and TryLoadProfiles so editor scripts can persist sprite settings without opening the window, with explicit overwrite and independent profile copies (#862).
  • Add UnityMethodAnalyzerReportExportAPI so editor scripts can save Markdown and JSON findings with compiler coverage status without opening the window (#861).
  • Add ValidationReportExportAPI so editor scripts can write Sentinel JSON and JUnit reports from a validation run without opening the workspace (#821).
  • Add SpriteSheetAnimationAPI.TryCreate so editor scripts can preview or create sprite sheet animation clips from ordered frames without opening the window (#821).
  • Add SpriteSheetAnimationAPI.TryDiscoverFrames so editor scripts can load ordered sprite frames and choose whether to update import settings without opening the window (#821).
  • Add AnimationClipFrameSaveAPI.TrySaveFrames so editor scripts can save sprite frames and FPS to an existing clip without opening the window. See Sprite Animation Editor (#851).
  • Add PrefabChecker.ScanFolders so editor scripts can inspect prefab findings from explicit folders and options without opening the window (#821).
  • Add PrefabChecker.TryRemoveMissingScripts so editor scripts can preview or repair missing script slots in prefabs without opening the window (#821).
  • Add TextureResizerAPI.TryResizeTextures so editor scripts can resize or preview PNGs from explicit textures and asset folders without opening the window. Textures process in natural name order (#821).
  • Add an encoding-aware DurableFile.TryWriteAllText overload that preserves the encoding's byte order mark for staged writes.

Fixed

  • Fix sprite atlas generation losing unsaved config sprite entries while creating an atlas asset (#821).
  • Fix the Sprite Atlas Generator's Force Uncompressed action so its default-platform compression setting stays uncompressed after reimport (#821).
  • Fix editor folder discovery and atlas generation so paths with Windows separators work from scripts and batch mode (#821).
  • Fix DurableFile ownership so append, create, and delete operations cannot change a destination during a staged write; competing operations also report failure without crashing Windows IL2CPP players (#871).
  • Fix Image Blur and Sprite Sheet Extractor output publication so a competing staged-file writer cannot change the destination during publication (#871).
  • Fix Sprite Sheet Animation Creator so declining an import-settings prompt leaves the texture importer unchanged (#821).
  • Fix Texture Resizer batch calls so whitespace-only source folder entries are ignored like empty entries (#867).
  • Fix cached package path conversion so blank package IDs return no path (#867).
  • Fix Sentinel validation runs so targets with whitespace-only GUIDs or asset paths are skipped (#867).
  • Ignore whitespace-only file paths in Unity Method Analyzer source selection, compiler messages, navigation, and report export (#867).
  • Fix SpriteRendererMetadata so material copies are released after replacement, pop, or destruction, and moving the top material to the back restores the prior top (#855).
  • Fix Sprite Cropper padding so borderless sprites stay borderless and larger outputs retain their full resolution (#857).
  • Fix competing Sentinel suppression edits and Undo so a stale action reports a conflict instead of replacing another action's decisions (#817).
  • Fix prefab validation Undo and stale-key repair rollback so they reject observed byte changes before restoring a snapshot; competing staged-replacement DurableFile writers share the check's lock (#817, #827).
  • Fix animation creation APIs to return the created asset path when saving fails after Unity creates the clip (#821).
  • Fix Image Blur and Sprite Sheet Extractor new image publication so readers see complete outputs and competing publishers preserve the first file (#827).
  • Fix the Sprite Animation Editor's Preview FPS field on Unity 2021.3 so its window opens correctly (#851).
  • Fix Sprite Animation Editor saves for clips with multiple sprite bindings so frames from other bindings are not written into the selected curve (#851).
  • Fix Odin Migration menu placement under Tools > Wallstop Studios > Unity Helpers (#849).
  • Fix JSON Type values and pool type settings to recover constructed generics after unique component assembly moves (#846).
  • Fix SerializableType references to constructed generic types after their component types move between assemblies (#845).
  • Fix Texture Resizer so it rejects invalid sizing settings and colliding output names before changing source, importer, or destination files (#821).
  • Fix Test Run Reporter summary claims so one run owns a marker and a stale callback cannot replace or delete a newer run (#827).
  • Fix concurrent processes publishing another DurableFile writer's staged bytes. A competing writer now fails while the first owns the stage (#838).
  • Stage subtype manifests, exported proto schemas, and sprite or animation configs before replacement so failed staging leaves the previous file intact (#827).
  • Stage original asset bytes before stale-key repair rollback and prefab validation undo, so failed staging does not truncate the asset (#827).
  • Keep issue template version choices stable across local hooks and release preparation by rendering both forms from a tracked version manifest.
  • Fix asset change detection warnings caused by deserializing imported assets while checking for matching sub-assets. Sprite and nested asset watchers still receive matching paths (#834).
  • Fix FileHelper.InitializePath to return false for invalid paths and directory failures while preserving existing files. See File Operations (#827).

Improved

  • Improve tuple-to-coroutine adapters by removing extra callback allocations (#781).
  • Improve Sprite Cropper scan speed for images of at least one million pixels (#782).
  • Improve JesseSort on large, sustained random-looking inputs with a direct-sort route while retaining pile merging for structured runs (#747).
  • Improve subset sampling from queues and hash sets by copying each source once (#781).
  • Improve loading of missing SerializableType references without building the type-picker catalog (#783).

[3.6.1] - 2026-09-21

Added

  • Add AnimationEventSaveAPI to save clip events and optional frame rates from editor scripts without opening the window. See Animation Event Editor (#821).
  • Add SpritePivotAdjusterAPI to find sprite textures and preview or apply center-of-mass pivots from editor scripts without opening the window. See Sprite Pivot Adjuster (#821).
  • Add SpriteCropperAPI to discover and crop sprite assets, build mappings for separate cropped outputs, and preview or replace references from scripts without opening the window. Failed encoding or staging writes keep the previous PNG bytes. See Sprite Cropper (#821, #827).
  • Add AnimationCreatorAPI to build sprite clips and write uniquely named animation assets from editor scripts without opening the window. See Animation Creator (#821).
  • Add ImageBlurAPI to blur textures or write blurred project assets from editor scripts without opening the window. Occupied output names are preserved. See Image Blur Tool (#821, #817).
  • Batch sprite reference replacement asset edits and finalize one Undo record per changed object (#821).
  • Add SpriteSheetExtractionAPI to find sprite sheets and extract selected rectangles from scripts or batch mode without opening the window. Outputs appearing during extraction are skipped unless overwrite is requested. See Sprite Sheet Extractor (#821, #817).
  • Add SpriteSheetReferenceReplacementAPI to preview and replace sprite references in explicit project assets from scripts. See Sprite Sheet Extractor (#821).
  • Add AnimationCopierAPI to analyze, preview, copy, and clean up standalone animation clips from scripts or batch mode without opening its window. See Animation Copier (#821).
  • Add FitTextureSizeAPI to preview and apply texture import sizes from scripts or batch mode without opening its window. See Fit Texture Size (#821).
  • Add ProtoSchemaExporter to write selected or discovered project contracts from Editor code without opening a window. See Serialization (#821).
  • Add a public sprite atlas API to scan and sync folders, generate and pack atlases, and detect config drift from code or batch mode. Matching atlases are left untouched. See Sprite Atlas Generator (#821).
  • Add DurableFile.WriteAllBytesAsync for staged, cancellable binary saves with the same durability guarantees as synchronous byte writes. See Durable Writes for Player Data (#816).
  • Add an Odin migration preview/apply tool for proven-equivalent ReadOnly and EnumToggleButtons fields. Preview reports possible Odin-owned data, including quoted keys, in scenes, prefabs, and assets; source-only edits leave that data untouched. See the Odin Migration Guide (#794).
  • Add opt-in WUH018 warnings for string == and != comparisons whose ordinal comparison policy is implicit. Null checks and explicit comparison APIs remain quiet. Every package-owned .NET project enforces all WUH### policies, including the three consumer opt-ins. See Analyzers (#807).
  • Add Analyzer Policies under Tools > Wallstop Studios > Unity Helpers. It explains every WUH### diagnostic, enables or disables the full policy set in Assets/Default.ruleset, preserves unrelated analyzer groups, and reports missing, mixed, duplicate, or unknown-rule drift.
  • Add WallMath.TryFisherExactTest for exact two-sided comparisons of two binary groups with bounded work and fail-soft validation. See Descriptive Statistics (#742).
  • Add WallMath.TryExactSignTest for exact two-sided paired-result comparisons that exclude ties and fail soft on invalid counts. See Descriptive Statistics (#742).
  • Add WallMath.TryClopperPearsonInterval for exact equal-tailed binomial confidence bounds with fail-soft validation. See Descriptive Statistics (#742).
  • Add stable exponential-race weighted index and no-replacement subset selection over spans, with allocation-free caller scratch and overflow-safe cumulative double selection. See Random Generators (#792, #781).
  • Add reusable named command-line parsing, RectTransform-to-BoxCollider2D synchronization, and DPI-aware EventSystem drag thresholds. See Helper Utilities and Math and Extensions (#742).
  • Add RandomSeeds.Mix64 and ordered numeric or UTF-8 domain seed derivation for reproducible, schedule-independent gameplay streams. See Keyed Seed Derivation (#791).
  • Add PointerEventData.TryGetWorldPoint and TryGetLocalPoint, which prefer raycast hits and convert clamped screen positions across overlay, camera, and world-space canvases. See Pointer Coordinates (#742).
  • Add Line2D.TryGetIntersectionPoint(Circle, out Vector2), which returns the first circle-boundary hit along a segment. See Math and Extensions (#742).
  • Add ShapeHelper circular curves, upper-semicircle fractions, and angle-centered circular fractions with reusable output buffers and fail-soft validation. See Helper Utilities (#742).
  • Add NextIntSkewed, which averages uniform draws with a preferred target and clamps the result to the requested bounds. See Random Generators (#742).
  • Add AuthoredAssetYaml.TryResolveObjectReference, which resolves both external and same-file Unity references without loading the asset. See Authored Asset Validation (#779).
  • Add ColorContrast.Composite, which produces the opaque visible color for a translucent foreground before a contrast measurement. See Readable Text on Any Background (#777).
  • Add optional report context and exception handler overloads to StartFunctionAsCoroutine, so hosted jobs can attribute or program against every failure (#778).

Improved

  • Speed up [DetectAssetChanged] watcher initialization by finding handler methods through Unity's type cache instead of inspecting every method in loaded assemblies (#783, #818).

  • Avoid a staging copy for ICollection<T> sources in OrderBy, Ordered, and Shuffled, while keeping compact returned lists for other sources (#781).

  • Correct random reference documentation: identify WyRandom's wyhash v1 stream, clarify XoroShiroRandom's high-half output, and distinguish the SplitMix design from Vigna's 2015 reference implementation without attributing an unsupported published PractRand result (#757).

  • Describe PatternDefeatingQuickSort as the pdqsort-inspired introsort it implements; it does not include upstream pattern-breaking heuristics (#756).

  • Avoid an iterator allocation when the Animation Viewer opens or restores its multi-file selector (#781).

  • Improve editor reloads by removing additional query stages from attribute metadata discovery (#648, #781).

  • Improve Sprite Atlas Generator refresh and source-sprite processing by removing temporary query pipelines while preserving configuration and sprite order (#648, #781).

  • Improve LayeredImage construction by keeping measured parallel blending for multi-row sprites while avoiding slower scheduling for single-row sprites; overflow-safe work sizing preserves the parallel path for very large dimensions (#781, #782).

  • Avoid Sprite Pivot parallel scheduling below 65,536 pixels and for one-row sprites, while retaining measured parallel speedups for larger work, and reduce overhead when Pivot Adjuster and Sprite Cropper search large asset folders (#648, #781, #782).

  • Avoid parallel scheduling for Image Blur passes with fewer than 256 pixels or only one partition, reducing small-texture overhead while retaining parallel execution for larger work (#782).

  • Resolve already-loadable SerializableType names before constructing the full descriptor catalog, and avoid parallel sprite-crop scans below a conservative measured crossover (#781, #782).

  • Reduce the slowest EditMode, PlayMode, and standalone test paths without reducing case, sample, or parameter counts or covered production behavior (#783).

  • Format Sentinel JUnit reports sequentially, removing worker scheduling and two temporary arrays while preserving report order (#781, #782).

  • Release destroyed Unity object targets from Sentinel's central result store on the next results read without discarding diagnostic details (#669).

  • Defer SerializableType ignore-pattern match counts until the settings window displays them, avoiding a scan of every loaded type on each domain reload (#786).

  • Improve Sprite Cropper and Sprite Sheet Extractor memory reuse for dynamically sized pixel output, with measured size-aware parallel row copies for large images (#754).

  • Remove avoidable allocations from asset watcher discovery and WallstopProto source generation while preserving handler and diagnostic ordering (#764, #781).

Fixed

  • Fix Sentinel suppression saves and undo leaving a partial decision file if a write fails (#827).
  • Fix Texture Resizer dry runs changing the loaded image and importer settings. A preview now calculates the target size without resizing or reimporting (#821).
  • Fix editor report exports leaving a partial file when writing fails. Existing Prefab Checker, Animation Copier, Method Analyzer, validation, and failed-test reports stay intact until the new report is ready (#827).
  • Fix FileHelper.CopyFileAsync erasing an existing destination when reading or staging fails, or the copy is cancelled. See File Operations (#827).
  • Fix Texture Resizer and Analyzer Policies saves when the destination changes during a write (#817).
  • Fix Animation Copier dry runs creating destination subfolders during copy previews.
  • Fix Animation Copier's Include Unchanged in Copy All option so selected unchanged clips are actually replaced while preserving destination GUIDs.
  • Fix DurableFile.TryDelete reporting success for a directory or inaccessible target (#817).
  • Fix Editor startup errors when Unity Helpers loads saved project settings, including custom test output paths and pool purging options (#814).
  • Fix NextSubset reading a lazy source when asked for zero items. It now returns empty without enumerating the source. See Random Generators (#781).
  • Fix plain .NET source builds of random generators and WallstopProto without the 3.6.0 upgrade workarounds. Unity builds keep their existing behavior (#815).

[3.6.0] - 2026-09-12

Added

  • Add WallMath.Median, Percentile, Mean and StandardDeviation over IReadOnlyList<float>, <double>, <int> and <long>, sorting pooled copies so the caller's list is never reordered. See Descriptive Statistics (#742).
  • Add SpriteHelpers.RotateTexture90, RotateTexture180 and ExtractSpriteRect, which produce new textures and leave the source untouched, returning null with a logged reason instead of throwing on unreadable or write-protected textures. See Helper Utilities (#742).
  • Add Objects.Sha256Hex for text (UTF-8) and byte payloads, and Objects.TrySha256HexOfFile, which reports a missing or unreadable file with false instead of throwing. The sprite sheet tooling's private file hash now calls it. See SHA-256 Digests (#742).
  • Add EditorUtilities.IsInvokedByTestRunner(), so an editor window can skip fixture-consuming OnEnable work during a test run instead of re-deriving it. Answers false rather than throwing where the command line is unreadable, and replaces seven private copies. See Editor Utilities (#742).
  • Fix ColorExtensions averaging (GetAverageColor, dominant and weighted variants) reading channel-swapped pixels from ARGB32 and BGRA32 textures through the raw fast path; those formats now take the same GetPixels32 path as every other format (#749).
  • Enforce one member ordering across the codebase: const, events, delegates, static properties, static fields, properties, fields, constructors, static methods, methods, nested types last, each tier public → protected → internal → private including const. npm run lint:nested-type-placement enforces it (#672).
  • Add a JesseSort adaptation with linear monotone-input handling and contiguous pile storage, credited to Jesse Lew. See JesseSort (#747).
  • Add runtime-length exclusion arrays to NextEnumExcept for interface and concrete generators. See Random Generators (#742).
  • Add BitOps, a public home for popcount, trailing zero count, floor log2, highest-bit isolation, power-of-two detection and power-of-two ceiling; the private copies in BitSet, ImmutableBitSet, the sorters and the sprite/enum tools now call it. See Helper Utilities (#742).
  • Add reusable protobuf read limits for encoded bytes, length-delimited fields, tag counts, nesting and cumulative packed-element counts across nested messages. See Serialization (#647).
  • Add reusable JSON read limits for nesting, per-container element counts and string or property-name lengths, with a parameterless constructor for generic factories. New Serializer.JsonDeserialize overloads refuse an over-budget document before decoding it. See Serialization (#647).
  • Add WUH017 warnings for a same-object GetComponent compared against null, where TryGetComponent answers the same question without Unity's Editor allocation. See Analyzers (#741).
  • Add opt-in exact sprite-art colliders, reusable alpha-mask reading and pixel-boundary tracing, with alpha and minimum-area controls. See Sprite Colliders (#715).
  • Add editor-only sprite keyframe times and threshold-based last-motion detection, with renderer-path filtering and selectable bounds edges. See Sprite Animation Motion (#717).
  • Add cross-assembly WallstopProto subtypes with static dispatch, inherited private-member support, and collision checks at compilation, project assembly and startup (#612).
  • Add malformed-state checks to WallstopProto raw remainder reads; failed reads leave an empty span and preserve the reader position (#647).
  • Add unbounded retention, custom key comparers and opt-in removal ownership transfer to Cache<TKey, TValue>. Shared cache limits now resize existing entries immediately (#703).
  • Add WUH015 warnings for invalid Unity callback signatures and WUH016 warnings for hidden inherited callbacks, using resolved types and inheritance (#654).
  • Add Helpers.ClearTagCache(), which drops every cached Helpers.Find(tag) lookup. A miss costs one GameObject.FindGameObjectWithTag (#643).
  • Add Buffers.ComparerPoolMaxDistinctEntries, which bounds how many distinct comparers the set and dictionary pool caches retain; the least recently used is evicted past it. See Comparer-Keyed Pools (#689).
  • Add StringWrapper.MaxCachedWrappers and StringWrapper.CachedCount, which bound and report how many distinct strings the wrapper cache retains; the least recently requested is evicted past it (#694).
  • Add SerializableTypeCatalog.MaxCachedFilterResults, which bounds how many distinct type-picker search terms are cached; the least recently used is evicted past it (#694).
  • Add PoolTypeResolver.MaxCachedTypeNames, which bounds how many distinct type-name spellings the pool type resolver retains (#643).
  • Add PoolStatistics.ApproximatelyEquals(other, tolerance), matching the overload PoolFrequencyStatistics already carries now that Equals is exact (#639).
  • Add RestorableGlobal<T>, which lends a global for a using block and takes it back exactly once, at any nesting depth and in any disposal order, so a copied scope can no longer restore a stale value. WUH014 reports the disposable structs that still assign in Dispose. See Restorable Globals (#627).
  • Add Tools > Wallstop Studios > Unity Helpers > Run EditMode/PlayMode Tests With Summary, which starts a test run and writes a pollable summary file to Temp/ for a script driving the editor from outside. See Test Run Reporter (#625).
  • Ship four validation rules with the package -- unfilled [WNotNull] slots, broken serializable dictionaries, empty animation keyframes, and script files that misname what they bind -- so the Asset Validation window and CI batch mode have something to check out of the box. See Asset Validation (#675).
  • Extend WUH003 to is null and is not null on a UnityEngine.Object. Both are CLR null tests, so a destroyed object reads as alive exactly as it does through ?.. A type pattern is not reported, because it is not a null test at all. See Analyzers (#621).
  • Add Tools > Wallstop Studios > Unity Helpers > Authored Assets, reports for a type nothing can author, an unfilled [WNotNull] slot, a SerializableDictionary that lost its pairing, and an empty keyframe. Reading is never destructive. A file a scan cannot read is named. See Authored Asset Validation (#618, #619, #620, #624, #631, #670).
  • Add Report Stale Serialized Keys and Repair Stale Serialized Keys In Selection, for keys an asset still records that no field claims. The repair rewrites one asset at a time, undoes any rewrite that loses objects or throws, and names which. See Authored Asset Validation (#617, #679).
  • Add WUH012, which warns when a walk of a serialized List<T> or T[] of references dereferences a row without testing it. Deleting the asset a row names leaves the row behind, empty. Compaction counts as the guard. See Analyzers (#628).
  • Add WUH011, which warns when SerializedStringComparer.compareMode changes after the comparer has been handed to a collection; freeze the comparer to keep existing keys reachable. See Performance Analyzers (#663).
  • Add SerializedStringComparer.Freeze(), which pins the comparison rule so a later write to compareMode cannot re-bucket a dictionary already built with it -- keys sorted under the old rule become unreachable under the new one, with nothing thrown (#646).
  • Add RTree2D.GetElementsWithCentersInBounds and RTree3D.GetElementsWithCentersInBounds, which return only elements whose Bounds.center is inside the query box, so a sweep over a tiling visits each element once. See Spatial Tree Semantics (#658).
  • Add Objects.StableHash32V1(bytes, seed), an FNV-1a hash whose answer depends on nothing but its arguments, so it is safe to persist or compare across machines. See Stable Hashing (#639).
  • Add ApproximatelyEquals(other, tolerance) to Circle, Sphere, Line2D, Line3D and PoolFrequencyStatistics, so a computed shape can still be compared loosely now that Equals is exact. The tolerance is the whole permitted per-component difference at every magnitude (#639).
  • A negative, infinite or NaN tolerance makes ApproximatelyEquals return false, and a non-finite component compares exactly, so a shape with an infinite radius is still approximately equal to itself (#639).
  • Add FastVector2Int.HasSameXY and FastVector3Int.HasSameXY, which say what the cross-dimensional Equals overloads did. Those overloads are now [Obsolete] and go away in 4.0 (#639).
  • Add Helpers.NameHashCode, the hash code that agrees with Helpers.NameEquals (#639).
  • Add TryNextUint, TryNextUlong, TryNextDouble and TryNextGaussian to AbstractRandom, reporting exhausted sampling, including nested integer rejection, with false and a default output. Existing methods retain degraded sampling (#638).
  • Add SpatialHash2D.TryInsert and SpatialHash3D.TryInsert, which report whether a position was stored. Insert drops a non-finite position silently: one that went NaN in physics is data, not a call the caller got wrong, so it never aborts the frame. See Query Contract (#642).
  • Add WUH010, which warns when a dictionary is read through its key indexer -- it throws for an absent key where TryGetValue reports, and match.Groups["name"] quietly hands back a group that never matched. Off by default, unlike the rest of the family. See Analyzers (#652).
  • Add WUH003 and WUH004, which warn when ?., ?[], ?? or ??= is applied to a UnityEngine.Object -- those operators test CLR null, so a destroyed object walks straight through the guard -- and when an NUnit null assertion passes over one. See Analyzers (#621).
  • Add WUH005, which warns on UnityEngine.Random, whose state no test can set or read without disturbing every other caller. It points at PRNG.Instance and the seedable generators behind IRandom. See Analyzers (#622).
  • Add WUH006 and WUH007, which warn when an EffectHandle or a coroutine handle is discarded -- each is the only thing that can undo the call it came from. The coroutine rule matches the return type, so this package's delay helpers count too. See Analyzers (#623, #626).
  • Add WUH008, which warns when a TryXxx out value is read on a path where the call's result was never tested. Only the BCL promises to write default on a miss. See Analyzers (#629).
  • Add WUH009, which warns when a teardown override calls base before statements that still need what it released. Setup chains base-first; teardown chains base-last. See Analyzers (#630).
  • Add Span<T> overloads of the IList helpers -- shuffling, Shift/rotate, TrySwap, Fill, predicate search, TryFindAll and TryPartition -- so a stackalloc buffer reaches them with no allocation. They share the IList bodies, so seeded output is unchanged. See Span Operations (#632, #651).
  • Add WPROTO047, a warning when a type inherits its WallstopProto contract and declares [WProtoMember] of its own without carrying [WProtoContract]. The code works; the attribute is what tells a reader those members are on the wire (#613).
  • Add a time-sliced validation engine with per-profile change/save triggers, build gates and JSON/JUnit reports. Failed, cancelled and incomplete runs retain the last complete result. See Asset Validation (#288, #648, #669).
  • Add the themed Sentinel workspace with rules, profiles, safe fixes, suppressions and editor status. Preferences can disable all Sentinel editor work and UI. See Asset Validation (#288, #634, #655, #669).
  • Add [WProtoSubtype(typeof(Base))], so a subtype joins a WallstopProto hierarchy without picking a field number. The editor assigns and commits the number on the next reload, and Assign WallstopProto Subtype Tags retires a removed one -- hand-numbered or not -- so it is never reused. See Polymorphism (#587, #601, #606).
  • Add Sfc64Random, the Small Fast Chaotic generator: a published-pedigree 64-bit generator with a very small hot path that answers NextUlong in one state advance. See Random Generators (#516).
  • Add [WProtoReserved], which records a field number or name a removed [WProtoMember] held -- or, on an enum, a removed member's value. Taking one again is a build error, not a save that reads back as the wrong thing, and the exported schema carries the proto3 reserved lines. See Retiring a member (#608, #609).
  • Subclass a [WProtoContract] and it serializes -- no attribute needed, the editor commits its field number. Write [WProtoNotSerialized] on one that never reaches the serializer. See Polymorphism (#613).
  • Add a proto schema exporter: Tools > Wallstop Studios > Unity Helpers > Proto Schema Exporter writes proto3 for your [WProtoContract] types, so anything downstream can read your saves. Search and tick the exact types, name a package, and write one file or one per assembly, namespace or type (#424, #595).
  • Reading a repeated WallstopProto field allocates from the elements a payload delivers, not the count it states, so splitting one across many runs cannot amplify (#647).
  • Add strict UTF-8 validation to WallstopProto strings and Uri: wire bytes that are not valid UTF-8 refuse the payload as malformed instead of decoding to replacement characters, as proto3 requires (#580).
  • Add IntMap<TValue>, an int-keyed open-addressing map measured at 1.26x–2.19x Dictionary<int,int> on hit-heavy lookups, with no comparer indirection on the lookup path. See Data Structures (#578).
  • Add char and Uri support to WallstopProto, byte-compatible with both protobuf-net majors, so links and code units inside a save no longer need a surrogate. DateTimeOffset, IntPtr, UIntPtr and Type stay refused, with the reasons in the serialization guide (#399).
  • Add stackTrace: false to Log, LogWarn and LogError, for a diagnostic that repeats once per object or once per frame. Unity captures a stack trace for every log by default, measured at 178.4 us against 13.3 us without one. See Logging Extensions (#564).
  • Add RandomGeneratorMetadata.Period, so every generator states its period where a caller can read it, and the Random Generators table now carries a Period column that cannot drift from it. A published period is quoted; where none exists the value states the measured live state width instead (#516, #285).
  • Add SerializedMemberNames, which converts between a property's source name and the <Name>k__BackingField Unity serializes it under, for anyone writing a drawer that resolves a member by name (#550).
  • Add WUH002, which warns when a Unity-serialized field resolves onto a collection of collections -- Unity drops every inner value and reports nothing. Covers public fields of nested [Serializable] types too. A warning at most, and switchable off. See Performance Analyzers (#548).
  • Add a non-throwing sibling for all eight ranged draws, NextIntInRange through NextDoubleInRange. They answer the low bound where the strict overloads reject an empty range, which is what an authored min/max pair set equal produces. See Ranges a Designer Authored (#546).
  • Add WUH001, which warns when a lookup factory is passed as a method group -- C# rebuilds that delegate on every call, hits included. Covers ConcurrentDictionary, ConditionalWeakTable and this package's IDictionary extensions. A warning at most, and switchable off. See Performance Analyzers (#538).
  • Add Serializer.ProtobufSurrogatesReady(), which reports whether protobuf will write the byte layout this package documents, and names the types it will not. A game can check it before its first save instead of reading a startup log. See Checking the surrogates took effect (#531).
  • Add ReflectionHelpers.CreateTypedArray(), which builds an array whose element type is known only at run time about twice as fast as Array.CreateInstance plus Array.SetValue (#529).
  • Add string.Slugify(), which produces a URL- and filename-safe key: lowercase ASCII, single hyphens, accents folded so "Café" is "cafe". ToKebabCase keeps punctuation and accents and is not a substitute. See Slugs (#386).
  • Add TrackedObjectPool<T>, a pool for UnityEngine.Object items whose lifetime ends in a callback rather than a scope. Dispose destroys what is still checked out instead of stranding it in the scene, and a destroyed item is never pooled or handed out. See Pooling Unity Objects (#523).
  • Add VisualElement.IsShown(), IsShownResolved(), IsWithin(), FocusedElement() and TryFocus(). Focus() is silent when it does nothing, and Unity's own Contains excludes the element itself, so "did focus move" and "is the focused element mine" both had to be written by hand. See UI Toolkit Extensions (#513).
  • Add Xoshiro128StarStar and Xoshiro256StarStar, the first generators here whose every output bit is strong enough for NextBool and power-of-two masks. Xoshiro256StarStar also answers NextUlong in one state advance instead of two. See Random Generators (#510, #285).
  • Add release notes to the Unity Package Manager: the package now carries its changelog section and a Changelog link, so Version History shows what changed instead of nothing (#421).
  • Add AOT protobuf support for ValueTuple: Serializer.ProtoSerialize((7, 1.5f)) threw ExecutionEngineException on an IL2CPP player and now goes through a generated formatter. JSON of a tuple stays editor-only. Define WALLSTOP_DISABLE_VALUE_TUPLE_SERIALIZATION to opt out. See Tuples serialize on IL2CPP (#289).
  • Add SerializableValueTuple<T1, T2> and SerializableValueTuple<T1, T2, T3>, so a tuple survives Unity serialization -- a (int, float) field is silently dropped, taking any authored contents with it. Byte-identical to ValueTuple in protobuf and JSON. See SerializableValueTuple (#289).
  • Add a comparer constructor to the Dictionary<TKey, TValue> nested in SerializableDictionaryBase, so base(new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase)) -- the documented way to seed a comparer -- compiles inside a subclass, where that type shadows the framework one (#476).
  • Add long, float and double overloads of WrappedAdd, and a long WrappedIncrement, so an angle or a normalized phase wraps with the same call a ring-buffer cursor does. See Numeric Helpers.
  • Add WDoomRandom, an index-into-array generator inspired by id's original DOOM technique: one array read per draw, and one index of state, so a save file records the whole generator. It repeats every 1024 draws -- for retro feel and replays, never sampling. See Random Generators (#281, #516, #524).
  • Add SortAlgorithm.Yam and IList<T>.YamSort(), a stable sort that approaches O(n) on sequential and reverse-sequential data. Adapted from YamSort by Gary Gende. See IList Sorting Performance (#461).
  • Add [SingletonCreation(SingletonCreationPolicy.NeverCreate)], which stops RuntimeSingleton<T>.Instance creating a bare stand-in when no instance exists. It returns null and names the type instead. See Controlling on-demand creation (#321).
  • Add AnimationClip.GetSpriteFramesFromClip(), which pairs every sprite a clip references with the binding that supplies it, a GetSpritesFromClip(path, propertyName, type) overload that filters on that binding, and UnityExtensions.SpriteBindingProperty. Editor-only. See Sprites from an AnimationClip (#451).
  • Add AssetChangeDetectionUtility.Enabled, ResetEnabledToDefault() and EnabledScope(bool) to turn the [DetectAssetChanged] watcher on and off. The returned AssetChangeDetectionEnabledScope restores the previous setting on dispose (#327).
  • Add SingleThreadedThreadPool.DrainAsync(), which closes the pool and waits for queued work to finish instead of dropping it, plus IsAcceptingWork (#318).
  • Add SerializableList<T>, a list that survives Unity serialization inside another serialized collection (#314).
  • Add the Inspector drawer for SerializableDictionary<TKey, TValue, TValueCache>, which previously had none (#314).
  • Add WGuid.TryCreate(Guid, out WGuid), which wraps an existing GUID without throwing when it is not version 4 (#437).
  • Add Enum.TryConvertToUInt64() and TryConvertToInt64(), which convert any enum value to its 64-bit bit pattern without overflowing on negative or very large members.
  • Add ColorQuantization, the one place an 8-bit channel becomes a normalized float and back. ToNormalized divides by 255, the same bits Unity's Color32 conversion gives; ToByte rounds to the nearest channel; ToThresholdByte bounds a float cutoff. See 8-bit Channels and Normalized Floats (#466, #565).
  • Add ColorQuantization.AreSameColor(), the one definition of "the same color" in the package: two colors match when they encode to the same 8-bit channels, which is the only rule an equality comparer can hash (#472).
  • Add ColorContrast, which answers whether text is readable on a background the way WCAG defines it: RelativeLuminance, ContrastRatio and ReadableTextColor. See Readable Text on Any Background (#471).
  • Add TextureResampling, the one place a resampler picks a source pixel and mixes colors of unequal opacity: BilinearSourceCoordinate, NearestSourceIndex, Premultiply and Unpremultiply. See Resampling a Texture (#470).
  • Add SemaphoreSlim.Acquire(), TryAcquire() and AcquireAsync(), so a permit can be taken with using instead of a finally. The lease releases its permit exactly once, however many copies of it exist. See Semaphore Leases (#358).
  • Add DurableFile, whose writes cannot leave a truncated file behind: TryWriteAllText, TryWriteAllBytes, TryAppendAllText, TryCopy, TryDelete and text/copy async equivalents. See Durable Writes for Player Data (#319).
  • Add WallstopProto (preview), an IL2CPP-safe, protobuf-net-compatible serializer. Hand-written formatters must consume valid complete payloads; enum registrations must match the actual enum type, width and signedness. See WallstopProto (#343, #647, #722).
  • Add steady-state allocation-free IndentLevelScope and internal label-width scopes that remain copy-idempotent and restore the newest active editor-global value even when nested copies are disposed out of order (#627).
  • Add facade-owned nested-message size plans to WallstopProto. Measured prefix widths avoid repeated large-payload moves, direct writers keep canonical back-patching, and a formatter that underwrites its measurement is refused (#383, #504).
  • Add the WallstopProto source generator (preview): annotate a type with [WProtoContract] and its formatter is generated into your assembly and registered for you. A contract it cannot serialize is a build error naming the fix. See The generator (#343).
  • Add WProtoFormatterProvider, which resolves IWProtoFormatter<T> without reflection. Built-ins cover FastVector2Int, FastVector3Int, WGuid, RandomState, DateTime, TimeSpan, Guid and decimal; later registrations replace them (#343, #379, #399).
  • Add support for a [WProtoMember] that is another [WProtoContract], including the same type again, so a tree or linked list serializes without a hand-written formatter. A sub-message merges into the value your constructor gave the member. See Contracts that hold other contracts (#380, #475, #485).
  • Add WallstopProto support for arrays and any ICollection<T> with a public parameterless constructor and Add, including struct collections, which are not boxed on the write path. OverwriteList = true replaces the constructor's collection instead of appending. See Collections (#343).
  • Add WallstopProto support for LinkedList<T>, Queue<T>, Stack<T>, ReadOnlyCollection<T>, ReadOnlyDictionary<K,V> and members declared as the matching interfaces. A Stack<T> round-trips in its original order. See Collections (#395).
  • Add WallstopProto support for nested and jagged collections — int[][], List<List<int>>, Dictionary<string, List<int>> and deeper — which protobuf-net refuses. See Collections (#399).
  • Add WallstopProto support for rectangular arrays — int[,], int[,,], and one anywhere a collection can go — which protobuf-net refuses. Dimensions travel with the elements, so new int[0, 5] keeps its shape. See Collections (#434).
  • Add WallstopProto maps: a Dictionary<,>, SortedDictionary<,> or your own IDictionary<,> is written as a protobuf map, byte-compatible with protobuf-net. See Maps (#387).
  • Add WallstopProto polymorphism: [WProtoInclude(tag, typeof(Subtype))] round-trips a member typed as a base as its concrete subtype, with no reflection. An unrecognized include tag is skipped, so saves from newer builds still load. See Polymorphism (#390).
  • Add [assembly: WProtoSurrogate(typeof(Real), typeof(Surrogate))] for wire shapes on types you cannot annotate, plus open generic pairs that cover each closed construction. Package pairs serve two- and three-component ValueTuple members and map keys. See Surrogates (#391, #399).
  • Add WPROTO038, a build error when an open surrogate pair has mismatched arity, openness, or stricter generic constraints than its real type (#399).
  • Add [assembly: WProtoRootMarshal(typeof(Real), typeof(Formatter))], which gives a type a different wire shape as the root of a serialization than as a member of a contract. See Root marshals (#402).
  • Add [assembly: WProtoDeclaredRoot(typeof(IRandom), typeof(AbstractRandom))], which names the contract serving a value held as an interface. It applies at the root only. See Declared roots (#403).
  • Add generic [WProtoContract] support: each closed construction gets its own encoding and is registered automatically, so your own Box<YourStruct> needs no manual registration. A member typed as the contract's own parameter merges a sub-message a payload carries twice. See Generic contracts (#385, #484).
  • Add Tools > Wallstop Studios > Unity Helpers > Validate Serialized Fields In Selection, which reports a public or [SerializeField] field Unity silently drops -- a Dictionary, a (int, float), a Nullable<T> -- and names the package stand-in to use instead. See Serialized Field Validator (#497).
  • Add generated JSON converters, so Deque<int> and SerializableDictionary<string, int> serialize in an IL2CPP player instead of throwing ExecutionEngineException. Define WALLSTOP_DISABLE_GENERATED_JSON_CONVERTERS to opt out. See Generic containers get their converters generated (#501).
  • Add bounded pooled scratch lists to generated JSON array converters, so a warm read only allocates its returned array and an untrusted large array is not retained by the pool (#504).
  • Add a metamorphic battery over WallstopProto: payloads rewritten into other legal spellings -- fields reordered, an undeclared field injected, a sub-message split into two that merge -- must decode unchanged (#462).
  • Add a differential fuzz over WallstopProto: random valid values for repeated, map and polymorphic contracts, checked in both directions against protobuf-net 2.4.9 and 3.2.56 (#437).
  • Add WProtoMemberAttribute.DataFormat, which selects a signed integer member's encoding. WProtoDataFormat.ZigZag is protobuf's sint32/sint64, where a small negative value costs one byte instead of ten. See DataFormat (#527).
  • Add WPROTO037, a build error for DataFormat = ZigZag on a member with no such encoding. Only sbyte, short, int and long have one, so anywhere else the annotation would have been dropped and the member written as the int32 it declined (#527).
  • Add WPROTO035 and WPROTO036, warnings for an [assembly: WJsonConverter] pairing that cannot be closed, and for two pairings claiming one type (#501).
  • Add WPROTO034, a warning for a lifecycle hook declared on a subtype of a [WProtoInclude] chain. protobuf-net 3 never runs one, and protobuf-net 2 runs it in the opposite order to WallstopProto, so declare it on the root instead. See The generator (#500).
  • Add WPROTO033, a warning for a SkipConstructor contract holding a field that is initialized where it is declared and is not a [WProtoMember]. That instance is allocated uninitialized, so the field arrives at its type's default. See The generator (#494).
  • Add WallstopProto support for readonly fields and get-only properties, previously a build error. The generator emits a private constructor into your partial type, keeps the parameterless one it would otherwise have had, and a member the payload omits keeps the value your constructor gave it (#394, #491).
  • Add WProtoContractAttribute.SkipConstructor, which reads a contract without running any constructor its author wrote, matching protobuf-net and still working under IL2CPP. A collection member holds exactly the elements the payload carried, unless a parent's constructor supplied the instance (#394).
  • Add WProtoContractAttribute.IgnoreListHandling, which writes a contract that also implements ICollection<T> as a message. Without it such a member is a build error naming both readings (#343).
  • Add IWProtoScalarFormatter<T> and WProtoGeneric<T>, which make a value's wire type expressible so a generic contract can encode a member whose type it cannot see. IWProtoFormatter<T> is unchanged.
  • Add IWProtoPolymorphicFormatter, which lets a formatter report the runtime types its [WProtoInclude] chain writes, so Serializer can serve a value held as its base (#403).
  • Add IWProtoConditionalFormatter, so a formatter reports whether it can encode a closure before anything is written. An element WallstopProto cannot encode now falls back to protobuf-net instead of throwing mid-serialization (#402, #416).
  • Add WProtoFacade.TryDeserializeAs(), the read that names a concrete type. Serializer.ProtoDeserialize<T>(byte[], Type) routes through it (#403).
  • Add SerializationCapacityLimits, the bound a deserializer applies to a capacity a payload claims, with MaximumRestoredCapacity (default 1,048,576) for games whose saves are legitimately larger. An empty rectangular array whose axis exceeds it does not deserialize; one carrying elements is unaffected (#437).
  • Add WProtoReader.CountPackedElements(), WProtoArrayBuilder<T> and WProtoRepeated.Reserve(), so a hand-written formatter can size a repeated field once instead of growing it. Generated formatters already size theirs from the packed run (#398).
  • Add WProtoReader.TryReadPackedRun(), which reads a packed repeated field's payload as its own reader without spending a nesting level.
  • Add WProtoMessageAccumulator and the matching WProtoReader.TryReadMessage(payload, formatter, out value) overload, so a hand-written formatter can merge a sub-message field a payload carries more than once (#475).
  • Add IWProtoMergeFormatter<T> and a WProtoReader.TryReadMessage(payload, formatter, seed, out value) overload, so a hand-written formatter can decode into a value the caller already holds. IWProtoFormatter<T> is unchanged; a formatter that skips this reads exactly as before (#485).
  • Add WProtoReader.MaxNestingDepth (64), the deepest message nesting a payload may use, so a few kilobytes cannot ask a formatter for thousands of stack frames. A hand-written formatter descends with TryReadMessage(formatter, out value) or new WProtoReader(payload, in parent), which carry the depth (#343, #377).
  • Add WProtoGeneric<T>.CanEncode, which reports whether a closed type argument can be encoded at all, IsMessage, which reports whether it is encoded as a sub-message, and a TryReadValue(ref reader, payload, out value) overload that decodes accumulated occurrences (#484).
  • Add WProtoRectangular, the shape check and refusal message a hand-written rectangular-array formatter needs.
  • Add WProtoRepeated.NullElement() and NullNestedElement(), which build the exceptions a generated formatter throws for a null repeated element or inner collection.
  • Add WProtoFormatterProvider.UnexpectedSubtype(), which builds the exception thrown for a value whose runtime type its contract does not declare.
  • Add WPROTO018, a build error when a [WProtoContract]'s base is one too but is not declared with [WProtoInclude] (#394).
  • Add WPROTO028, a warning when a closed construction cannot be registered because it closes over a private nested type. It is skipped rather than failing the build (#414).
  • Add WPROTO030, an informational diagnostic when a protobuf-net contract has no [WProtoContract], so migration is an opt-in worklist. It reads [ProtoContract], a vendored protobuf-net under a renamed namespace, and a [DataContract] with ordered [DataMember]s, and names which matched (#407, #597).
  • Add WPROTO031, a warning when assemblies declare different roots for the same type (#419).
  • Add WPROTO032, a build error when a member's collections nest more than 64 deep, which is deeper than the reader can read back (#399).

Security

  • Ship the runtime assembly with allowUnsafeCode off. Two BMI2 pointer branches guarded by framework symbols no Unity player defines were the only reason it was on, so nothing that ever ran has changed (#637).
  • Clamp or refuse a capacity a payload claims in Deque, SparseSet, BitSet and ImmutableBitSet. Six bytes claiming int.MaxValue previously allocated 8-16 GB and crashed the player. Raise SerializationCapacityLimits.MaximumRestoredCapacity if your saves exceed 1,048,576 elements (#429).

Changed

  • Change Editor MatchColliderToSprite.OnValidate to preserve collider geometry; programmatic callers now use RebuildCollider(). Inspector edits and the match button still rebuild immediately with Undo support. See Sprite Colliders.
  • Reduce script compilation work for generic protobuf and JSON contracts (#706).
  • Change Unity Method Analyzer to read compiler diagnostics, with assembly coverage, explicit recompilation, and navigation to resolved source locations. See Unity Method Analyzer (#654).
  • Pool purge sizing now averages every rental sample inside RollingWindowSeconds rather than the most recent ten thousand, so spike detection reads a longer history on a busy pool (#693).
  • Restoring a CyclicBuffer from JSON whose stated capacity is below the items it carries now keeps every item, matching what both binary paths already did (#637).
  • StringWrapper.Dispose no longer removes the wrapper from the cache, and is [Obsolete]. The wrapper is shared with every other holder of the same string, so one borrower's using block evicted an entry the rest were still reading. Use StringWrapper.Remove or Clear (#646).
  • Restrict Equals(object) on Attribute, WGuid, FastVector2Int, FastVector3Int, SerializableType, SerializableNullable<T> and both SerializableValueTuple arities to its own type. A boxed float, Guid, Type, Vector2Int or ValueTuple never answered true in return; the typed overloads are unchanged (#639).
  • Change Attribute to hash its CurrentValue, so an attribute already sitting in a Dictionary or HashSet becomes unreachable the moment a modification changes that value. Re-add it after applying one (#639).
  • Restrict CompareTo(object) on FastVector2Int and FastVector3Int to its own type, so it can no longer answer 0 for a pair Equals refuses. The cross-dimensional CompareTo overloads are now [Obsolete] and go away in 4.0 (#639).
  • Say what Objects.HashCode actually promises: the mixing is fixed, but each argument contributes an ordinary GetHashCode, which is process-local for strings, Unity objects and your own types. It was documented as deterministic and offered for save files (#639).
  • Change PRNG.Instance to return Xoshiro256StarStar: an Excellent-rated generator with a published 2^256-1 period and reference, measured even with the previous default. Streams drawn from PRNG.Instance differ; construct a generator directly to keep one (#516).
  • An attribute with only additive modifications recalculates 2.85x faster: 0.4563 us before, 0.1600 us now, measured on 6000.4.6f1. Addition, Multiplication and Override each got a full pass over every modification whether or not any carried that action (#529).
  • A relational field that finds nothing is ~15x cheaper to assign: 366-431 us before, 25.1 us now, measured on 6000.4.6f1 against a control that did not move. Unity captured a stack trace for the error log on every assignment, and for a collection field finding nothing is a normal state (#564, #529).
  • RomuDuo now implements published romuDuo, so a given seed produces a different sequence than in 3.5.1. No saved seed or RandomState carries a sequence across this change; pin a generator whose stream is unchanged if you need one to (#509).
  • DisjointSet.TryGetAllSets() is 2.4x-3.2x faster and allocates no temporary lists. It gathered every element into a per-root scratch list and then copied all of them a second time; elements now go straight into their result list, found through a dense index rather than a hash lookup (#309).
  • Serializer.ProtoSerialize(value, ref buffer) no longer allocates a second full payload for the Serializable collection types. The overload exists so a per-frame serialize allocates nothing (#504).
  • Serializing a SerializableDictionary or SerializableHashSet no longer resolves its protobuf wrapper type by reflection on every call; the type and its constructor are resolved once per element type (#504).
  • Writing a WGuid to JSON no longer allocates a 36-character string per value, and reading an AnimationCurve keyframe no longer boxes its weightedMode (#504).
  • Relational fields typed as an interface no longer fetch every component on the object and type-test each one. Unity's own query resolves an interface, so the query is 1.35x-2.49x faster depending on how many components the object carries -- and the child shape pays it once per descendant (#529).
  • Relational collection fields typed as a base component -- Collider2D[], Renderer[] -- no longer fetch every component on the object and type-test each one. Unity's own query already resolves a base class, so assignment of a component with three such fields is 9% faster (#529).
  • Animator.ResetTriggers() no longer allocates. It read Animator.parameters, which builds a new array and new element objects on every read -- 53.5 bytes per call for a three-parameter controller. The trigger hashes are now read once per controller (#549).
  • GameObject.IsDontDestroyOnLoad() no longer allocates. It read Scene.name, which marshals a fresh managed string every call, and the signature reads cheap enough to end up in Update. The answer now comes from the scene's handle (#549).
  • WGroup and WGroupEnd now accept fields only. They advertised properties, which nothing lays out, so the attribute compiled and drew nothing. Use [field: WGroup(...)] on an auto-property (#550).
  • WShowIf now reads a condition that names a [field: SerializeField] auto-property from the serialized state rather than the live object, so a pending Inspector edit is what it reacts to (#550).
  • Draw 64-bit values 2.49x faster from BlastCircuitRandom, RomuDuo, SplitMix64, WyRandom and Xoshiro256StarStar -- one state advance instead of two. NextUlong, NextLong and NextDouble return a different sequence for a given seed; 32-bit draws are unchanged. See Seeded streams that moved (#509).
  • Return the strong half of XoroShiroRandom, raising it from Fair to Good: its NextBool() was predictable from 128 draws, and PractRand now runs clean through 8GB where it used to fail at 16MB. Every draw changes for a given seed (#509, #285).
  • Stop the serializer allocating a delegate on every collection serialize. Four cache lookups built their factory per call rather than reusing one, costing 106-116 bytes each time, cache hit included (#504).
  • Assign a hierarchy 30% faster when most of its components have no relational fields: AssignHierarchy took a lock per component just to answer "does this type have any", costing 59.7 ns per component against 41.5 ns now (#529).
  • Deserialize a SerializableDictionary, SerializableSortedDictionary, SerializableHashSet or SerializableSortedSet from JSON without reflection: the converters looked their backing fields up on every read, costing 1.5 us of a 17.6 us 16-entry read (#504).
  • Stop [ChildComponent] and [ParentComponent] collection fields allocating a Component[] on every assignment, so a scene full of components no longer builds garbage during Awake (#534, #529).
  • Assign relational array fields faster: a [SiblingComponent] array field costs 22% less, and sibling collection fields no longer allocate per call. List and HashSet fields are unchanged (#529).
  • Write FastVector2Int and FastVector3Int components as sint32, so a negative coordinate costs one byte instead of ten. A 1,000-cell tilemap centred on the origin falls from 14,690 to 3,870 bytes. Payloads written by 3.5.1 still read (#527).
  • Stop FastVector2Int and FastVector3Int writing their cached hash, which every reader already recomputed: a 1,000-cell tilemap falls from 14,167 to 5,870 bytes. Payloads written by 3.5.1 still read; a 3.5.1 build cannot read new ones (#519).
  • Remove an item from a SpatialHash2D/SpatialHash3D bucket by swapping the last entry into its place rather than shifting the tail. Query results were never ordered and are not now; the order two items in one cell come back in can differ from before.
  • Say what has actually been measured about IllusionFlow, StormDropRandom, PhotonSpinRandom, FlurryBurstRandom and BlastCircuitRandom. All five are now verified clean through 8GB of PractRand 0.95 here, rather than described by an author's claim on a repository that is offline. Ratings are unchanged (#286, #516).
  • Take every Bounds, BoundsInt, Rect and Color extension receiver by in. Calling one no longer copies the struct at your call site, which ErrorProne.NET.Structs reported as an EPS06 warning you could not fix without giving up extension syntax (#512).
  • Make PooledArray<T> and PooledResource<T> readonly struct, so reading array, length or resource and calling Dispose() no longer copies the wrapper first (#512).
  • Lower XoroShiroRandom to Fair and RomuDuo to Good. Bit 0 of XoroShiroRandom follows a linear recurrence of order 128, so its NextBool() is predictable from 128 draws; prefer Xoshiro128StarStar where single bits matter (#509, #286).
  • Lower SquirrelRandom to Fair. It fails PractRand at 1GB, reproducibly across four seeds; it stays a good fit for the table lookups it was designed for (#286).
  • Read cancellation-aware JSON files through a pooled scratch buffer and deserialize the pooled stream's valid segment directly. Large save files no longer allocate an extra full-payload copy before decoding (#504).
  • Speed up every pooled-buffer operation on blittable elements -- the sorts, Shuffle, Fill and the geometry helpers -- by clearing a returned array only when its element type can hold a reference. A reference element is still never left rooted in the pool (#482).
  • Speed up ProtoSerialize, ProtoDeserialize, ProtoEquals and NextEnum by resolving each one's type questions once per closed generic instead of on every call. Measured at 8.5x for that check alone on reference types (#346).
  • Every IList<T> sort now runs over an array rather than the list's indexer, so sorting a T[] is 2.5x to 5.4x faster and needs no copy. Any other list is copied through a pooled buffer and back. See Where the Time Actually Goes (#463).
  • Speed up IList<T>.Reverse (26x-37x), Shift/RotateLeft/RotateRight (2.7x-36x), Fill (3.6x-38x), Shuffle (1.5x-2x) and predicate IndexOf/LastIndexOf (2.4x-3.1x) with bulk array operations. See Bulk Operations on a List (#480).
  • Enable WallstopProto by default for the runtime assembly: every Serializer.ProtoSerialize and ProtoDeserialize overload, including ref buffer and forceRuntimeType: true, uses generated formatters when available and falls back to protobuf-net (#343, #403).
  • Serialize thirty of this package's own contracts through WallstopProto when WALLSTOP_PROTO is defined, including AbstractRandom and all seventeen generators. Saved data is unchanged (#394).
  • Serialize SerializableHashSet, SerializableSortedSet, SerializableDictionary, SerializableSortedDictionary, Deque, CyclicBuffer and SparseSet through WallstopProto when WALLSTOP_PROTO is defined. Saved data is unchanged (#402).
  • Serialize the fourteen surrogated Unity and package structs -- Vector2, Bounds, Resolution, Parabola, ImmutableBitSet and the rest -- through WallstopProto when one is the root of a ProtoSerialize. An IL2CPP player threw or silently returned a default. Existing saves still read (#696).
  • Allow a struct backing set in SerializableSetBase<T, TSet>: the where TSet : class constraint is gone (#388).
  • Report a read of the write-only GameObject and Touch JSON converters as NotSupportedException instead of NotImplementedException (#437).
  • Ship the bundled System.Text.Json and friends only on editors that do not provide them; Unity supplies its own from 6000.5. See Bundled Assembly Conflicts (#331).

Fixed

  • Fix reusable tag and effect query buffers retaining stale entries when a target, handler, or requested tag is unavailable (#633).
  • Fix LineHelper.SimplifyPrecise leaving a supplied buffer stale for null or short paths, and allow both polyline simplifiers to use their input list as the destination (#633).
  • Fix per-destruction array allocation when cosmetic effects remove tracked targets. Cleanup still visits the original target snapshot when callbacks change the target list (#781).
  • Fix package compiler and analyzer warnings across supported Unity versions, including the 2021.3 serialization attribute conflict and Unity 6 obsolete build-target calls (#766, #770).
  • Fix the seven UI Toolkit progress controls failing to compile on Unity 6000.6. Their UXML tags and attributes remain available on Unity 2021.3 and newer (#759).
  • Fix pooled serialization writes accepting negative or oversized advances and overflowing capacity calculations (#760).
  • Fix caught exceptions losing their type, inner exceptions, and stack trace in diagnostic logs (#762).
  • Fix global budget enforcement running pool purge and disposal callbacks while holding the registry lock (#752).
  • Fix a disposed pooled serialization writer hanging when code tries to use it again (#753).
  • Fix the protobuf byte-writer pool abandoning its rented buffer on purge: writers now return their ArrayPool<byte> rental when they leave the pool, and the pooling guide documents when a pool needs onDisposal (#749).
  • Fix collection-inspector button textures leaking across script reloads. See Property Drawers (#648).
  • Fix unbounded sprite preview retention in Animation Creator and Animation Event Editor while preserving full-resolution previews. See Animation Tools (#648).
  • Fix WyRandom.Copy() losing pending Gaussian, bool, and byte values, so a copied generator continues the same mixed draw sequence (#638).
  • Fix malformed SparseSet binary saves silently losing invalid element IDs or enlarging their universe for duplicates. Both binary readers now refuse invalid elements before restoring the set (#647).
  • Fix explicit-type protobuf collection reads bypassing wrappers, preserving stored elements and capacity rules (#647).
  • Fix infinite-bound random draws returning NaN or excluded endpoints, and stop exhausted sources from multiplying retry budgets (#638).
  • Fix exact relational component matching when AllowInterfaces is disabled, including concrete types that are not sealed (#733).
  • Fix editor windows and collection drawers retaining owned serialized state and copied previews after refresh, close or failed initialization; shared Unity previews remain available (#734).
  • Fix cleared runtime singletons being returned again before frame-end destruction. Immediate lookup selects a live replacement or respects NeverCreate (#729).
  • Fix finite extreme coordinates disappearing from spatial queries or choosing farther nearest neighbors. Immutable trees preserve accepted finite entries across overflowing bounds and distance calculations (#720).
  • Fix runtime and ScriptableObject singleton reset callbacks interrupting cleanup or recursively invoking themselves. Reset completes after callback failures and rejects worker-thread access before changing state (#723).
  • Fix stuck progress bars after failed prefab checks, sprite and texture batches, atlas operations, reference replacement, and animation copy or delete cleanup (#648).
  • Fix infinite stored geometry hiding finite spatial-tree entries. Constructors exclude non-finite positions and bounds edges while preserving source identities; infinite query radii remain supported (#718).
  • Fix NaN guards in random sampling, sprite extraction, parabola math, spatial queries and cache timing. Nonfinite coroutine intervals use the existing one-frame delay. See Numeric Guards (#716).
  • Fix package stylesheet and asset paths for local checkouts outside the Unity project. Cached packages resolve through their package identity, and similarly named sibling projects no longer count as project content (#655).
  • Fix child-component binding to select nearer matches first for single fields and capped collections (#709).
  • Fix typed reflection invokers for inherited receivers and struct interface methods. Receiver-specific caching and exact return-type validation prevent incompatible delegates (#644).
  • Fix Range<T>.Overlaps for coincident open intervals, excluded touching endpoints, and empty or inverted ranges (#707).
  • Fix SLRU cache segment accounting during eviction and preserve protected-segment limits after resizing or clearing (#703).
  • Fix Image Blur batches to restore source texture import settings, release temporary textures, and show one completion message per batch (#648).
  • Fix FluxSort overflowing the stack on a list sorted by a shared key. 100,000 equal values recursed 47,572 frames deep; a StackOverflowException is caught by nothing (#645).
  • Lower a deserialized BitSet's capacity to the words the payload actually delivered, so corrupt saves fail reads safely and later writes still grow. An index too large to represent now returns false instead of throwing (#647).
  • Fix inspector button colours leaking a texture per intermediate shade: dragging a [WButton] palette colour picker minted a 1x1 texture the editor never released (#701).
  • Bound the drawer caches that grew once per inspected object -- foldout animations, measured property widths and built foldout keys -- so a long editor session no longer retains every object it ever showed (#701).
  • Fix StringWrapper.Get keeping every string it was ever given alive for the process, so wrapping a value built from gameplay grew without bound. A wrapper re-created after an eviction still equals the one it replaced (#694).
  • Fix SerializableTypeCatalog.GetFilteredDescriptors retaining a filtered copy of every project type for each distinct search term, so typing a name one character at a time kept an array per prefix (#694).
  • Fix TagHandler.RemoveTag throwing when a tag-removed handler removed another tag through the same buffer, which left the first tag raised. It also returns the handles it removed rather than the nested call's (#640).
  • Fix a cosmetic component destroyed by a sibling's callback aborting the whole effect, so an instant effect left its tags raised and applied no modifications (#640).
  • Fix AttributesComponent.ForceApplyAttributeModifications and ForceRemoveAttributeModifications throwing NullReferenceException for a handle whose effect is missing (#640).
  • Fix PoolStatistics.Equals comparing three rates within a tolerance, so a value did not equal itself when a rate was NaN and equality was not transitive. It is exact now, with ApproximatelyEquals(other, tolerance) for a loose compare (#639).
  • Fix AttributeEffect.Equals and CosmeticEffectData.GetHashCode throwing MissingReferenceException for a destroyed asset, which could throw out of a dictionary lookup (#639).
  • Fix AnimatedSpriteLayer.Equals throwing NullReferenceException when either side was a default value, so comparing two entries of a fresh array threw (#639).
  • Fix a default ImmutableBitSet comparing unequal to an empty one, so a default value did not equal its own round trip (#639).
  • Fix SplitMix64 comparing by reference through Equals(object) while hashing by value, so two generators with the same seed landed in two HashSet entries (#639).
  • Fix one element whose position or bounds went non-finite hiding every finite element from QuadTree2D, RTree2D and RTree3D, and RTree3D throwing when no element was finite. A non-finite element is skipped (#642).
  • Fix a query radius above about 1.8e19 returning elements outside it, on all eight trees and both spatial hashes: squaring it saturated to infinity and disabled the exact distance test (#642).
  • Fix OctTree3D throwing and QuadTree2D silently answering empty for an unusable boundary. Both now compute bounds from the elements, as passing no boundary does (#642).
  • Fix ReflectionHelpers.TryResolveType caching names no assembly declares, so a save file naming a renamed type grew the cache for the process. A name that only becomes resolvable after a later assembly load now resolves (#643).
  • Fix eight editor drawer caches growing for the life of the editor, several holding a SerializedProperty or dropdown option and so keeping an inspected asset or scene object alive (#643, #694).
  • Fix Helpers.Find(tag) keeping an unloaded scene's objects alive: an entry whose object the unload destroyed is dropped, where before only the next lookup of that same tag would notice. Helpers.ClearTagCache() drops them all (#643).
  • Fix every pooled rental allocating on a growth boundary for its pool's first ten thousand rents, and each pool then retaining 131 KB of usage samples for the process. Rentals are allocation-free once a pool exists (#693, #643).
  • Fix TextureScale.Bilinear and Point returning a partly scaled texture with no error when a worker slice failed. The failure now reaches the caller, as it already did on a single-core machine (#691).
  • Fix the [SiblingComponent], [ChildComponent], [ParentComponent] and [ValidateAssignment] field caches being plain dictionaries, so assigning components from more than one thread could corrupt them (#644).
  • Fix an ImmutableBitSet whose serialized capacity exceeded the words stored beside it throwing IndexOutOfRangeException from TryGet and All. The capacity is bounded by the words delivered (#637).
  • Fix a [SiblingComponent], [ChildComponent] or [ParentComponent] field declared private on a base class never being assigned, and never logging that it was not. Inherited [WNotNull], [ValidateAssignment] and Attribute fields are found now too (#688).
  • Fix TextureScale.Bilinear and Point returning their pooled buffers while worker threads were still writing into them, so a scaled texture could carry another caller's pixels (#643).
  • Fix a dropdown drawer keeping its choices list after returning it to the pool, so another editor could be handed the list it was still displaying (#643).
  • Fix five pooled rentals being dropped rather than returned when the code between the rent and the release threw (#643).
  • Bound the comparer-keyed set and dictionary pool caches, which kept every comparer a game ever handed them -- and any scene object one captured -- alive for the process. Buffers.ComparerPoolMaxDistinctEntries sets the bound (#689).
  • Fix Partition taking two pooled leases per partition and releasing one, so a consumer that did not dispose each partition leaked a lease slot per partition (#689).
  • Fix a ScriptableObjectSingleton whose first load threw staying broken for the rest of the session: the failure is now logged and Instance answers null, and clearing the instance recovers (#644).
  • Fix Instance handing back a ScriptableObjectSingleton asset destroyed by a reimport, which HasInstance already reported as absent. It reloads instead (#644).
  • Fix [AutoLoadSingleton] running only in the first play session when Enter Play Mode Options skips domain reload (#644).
  • Fix an effect that a subscriber removes from inside an apply callback leaving an attribute changed, or a tag raised, with no active effect and no way to undo it (#640).
  • Fix removing an effect clearing a tag another active effect still owns, when the first effect only got part-way through applying its own (#640).
  • Fix a shared CosmeticEffectData being applied twice and removed once when an effect is re-applied, which is what the default Refresh stacking mode does (#640).
  • Fix an attribute callback that adds or destroys a sibling AttributesComponent aborting the rest of the effect's removal, leaving its tags raised and its cosmetics orphaned (#640).
  • Fix NextSubset returning empty or another caller's values for a source that is not a list. Arrays and lists were never affected (#643).
  • Fix PolygonCollider2D.Invert losing its pooled buffers when Unity rejects a path (#643).
  • Fix a single-valued [ChildComponent] field binding a disabled component that the same attribute on a collection field excluded (#644).
  • Fix every relational field in a scene being left unassigned, silently, when an injected AttributeMetadataCache had been destroyed (#644).
  • Fix a singleton first touched from inside another singleton's OnInstanceCleared aborting startup cleanup for every singleton after it (#644).
  • Fix a default NativePcgRandom -- an unassigned field, an array element -- being a stuck generator rather than a seeded one: every draw returned the same value, in range and so not obviously wrong. Explicitly seeded streams are unchanged (#638).
  • Fix editor work that only ran on a tick an unattended or CI editor may never pump: a failing run exported nothing, drawer caches survived a domain reload, and old selector history was never pruned. All three now run as soon as the editor can answer (#684).
  • Fix IList<T>.Sort, which documented "No allocations": any list that is not a T[] is copied through a pooled buffer. Its docs now state that, and every SortAlgorithm member says whether it is stable. See Sorting Performance (#645).
  • Fix RemoveAtSwapBack mutating single-item, out-of-range and fixed-size lists before throwing. Failed removals now leave the list untouched (#645).
  • Fix WallstopGenericPool<T> returning an item into a pool that its onRelease callback just disposed. The item is retired exactly once instead of being resurrected inside a dead pool, in both thread-safe and SINGLE_THREADED builds (#643).
  • Fix RuntimeSingleton<T> accepting a sibling component that inherited the wrong closed generic base. The mismatch is refused and reported instead (#637).
  • Fix runtime and ScriptableObject singletons creating or loading a new instance while the application is quitting. An instance already found remains available during teardown (#644).
  • Fix races in the package logger. Its global flag, per-object disable set, metadata cache and scratch dictionary were unsynchronized, so a worker could miss a toggle, throw from inside a diagnostic, or print one object's fields under another's name (#646).
  • Fix ToCachedName and ToDisplayName growing a process-lifetime cache for undefined and composite enum values. Only declared members are cached now; anything else is formatted fresh (#646).
  • Fix UnityMainThreadDispatcher.RunAsync naming the caller's token on every cancellation. It now reports the token that actually cancelled the work, so a caller racing its own timeout against an unrelated one can tell them apart. See Which Token Cancelled (#641).
  • Fix SceneHelper scene disposal hanging forever when the dispatcher queue was full or the unload failed. Disposal now faults with the reason, and disposing a scope twice unloads the scene once (#641).
  • Fix eight editor windows -- Image Blur, Sprite Settings Applier, Texture Settings Applier, Animation Copier, Sprite Cropper, Animation Creator, Sprite Pivot Adjuster and Sprite Sheet Extractor -- leaking a SerializedObject on every close. Each now releases it and rebinds when reopened (#641).
  • Fix RTree3D.GetElementsInBounds dropping an element that straddles the query box: it filtered by the element's center where RTree2D returns anything the box touches, so a system ported from 2D to 3D silently stopped seeing them. Both now mean "the element's box touches the query box" (#658).
  • Fix spatial hash queries that never returned: a huge or infinite radius, or a rect spanning the float range, walked every cell in the volume. Each query now walks the cheaper of the cells and the occupied buckets. See Query Contract (#642).
  • Fix nearest-neighbor queries dropping equal-valued elements: all six spatial trees staged results in a value-keyed set, so 64 identical points answered one neighbor. They now return min(count, n), ordered by distance then insertion index (#642).
  • Fix negative, NaN and non-finite spatial query inputs returning an arbitrary subset. Every query clears its destination once and returns empty, RTree2D zero-range returns only elements the point touches, and RTree3D bounds queries include the max face like their siblings, zero-size elements too (#642).
  • Fix RTree2D and RTree3D range queries measuring with an epsilon. RTree3D dropped an element sitting exactly on the query radius along an axis, and both admitted one up to 1e-3 world units outside a small radius. Distances are compared exactly now (#642).
  • Fix a spatial hash's Dispose destroying the process-wide buffer pool every other consumer of that element type shares; it now releases only its own buckets. A NaN or infinite cell size is also rejected instead of collapsing the grid (#642).
  • Fix Circle, Sphere, Line2D, Line3D and PoolFrequencyStatistics comparing equal while hashing apart, so a value could vanish from the set it had just been added to. Equality is exact now; use ApproximatelyEquals for a tolerance (#639).
  • Fix Attribute hashing by reference identity while Equals compared CurrentValue, which put two equal attributes in different dictionary buckets. It hashes CurrentValue now (#639).
  • Fix AttributeEffect.Equals ignoring periodicEffects, behaviors and all four stacking fields, and its hash ignoring the name it compares. Equality reads every authored field now, and the hash reads the managed ones (#639).
  • Fix CosmeticEffectData hashing its component count while Equals compared a deduplicated type set, so one copy of a component and two compared equal and hashed apart (#639).
  • Fix an effect's stack key becoming unreachable once the effect asset was destroyed. The key compared managed identity and hashed through Unity's own GetHashCode, which collapses on destruction (#639).
  • Fix default(RandomState) hashing zero while comparing equal to new RandomState(0). The hash comes from the members Equals compares, never from the value a payload carried, and skips a gaussian the state says it does not hold (#639).
  • Fix AnimationEventEqualityComparer.GetHashCode(null) throwing while its Equals reported two nulls equal, which made HashSet<AnimationEvent>.Add(null) fail. Null hashes to zero (#639).
  • Fix SerializableType.Equals(null) and SerializableNullable<T>.Equals(null) answering true. A boxed value type is a real object, so Object.Equals requires false; test emptiness with IsEmpty or HasValue (#639).
  • Fix effect teardown that ran your callbacks before the handle was detached: one that removed, re-applied or queried its effect saw it still active, recursed, corrupted a pooled buffer, or threw and stranded the tags after it. ApplyEffect refuses a Stack past maximumStacks. See Removal Is Two-Phase (#640).
  • Fix IRandom.Next() and NextLong() returning int.MaxValue and long.MaxValue, one value outside their documented [0, max) range. Values from those two methods change for a given seed; raw NextUint/NextUlong streams and saved generator state do not (#638).
  • Fix guards a destroyed object walked straight through: ?. and ?? compare against CLR null, so TagHandler, EffectHandler, SingletonAutoLoader, two property drawers and the settings asset acted on effects, windows and assets that were already gone (#621).
  • Fix RuntimeSingleton<T>.Instance handing back a live-but-inert GameObject when the singleton component could not be attached. The half-built object is destroyed and Instance returns null instead. (#629).
  • Fix relational component assignment falling back to reflection after its metadata cache was destroyed: a destroyed _metadataCache field skipped the live AttributeMetadataCache.Instance instead of using it. (#629).
  • Fix the Animation Creator treating a frame index too large for an int as index 0, where it collided with a real frame 0. Such a name is no longer read as an indexed frame. (#629).
  • Fix reads of TryXxx out values whose call was never tested, including a RollingHighWaterMark cleanup that could subtract a sample nobody wrote and then loop forever (#629).
  • Fix two documentation examples that could not work as printed: the enum display-name sample imported Core.Attribute rather than Core.Attributes, and the link.xml sample preserved an assembly name that does not exist, which strips silently (#441).
  • Fix fifteen broken documentation links on AssetDatabaseBatchScope: its <see cref> references to AssetDatabase.Refresh, CreateAsset and ImportAsset named an ambiguous overload or an unresolvable type, so an IDE linked the wrong overload or nothing (#594).
  • Fix the IntelliSense tooltip on ten public ReflectionHelpers delegate factories, which carried two <summary> tags and showed the vaguer one (#441).
  • Fix zero-valued ValueTuple components and fixed-width map keys being omitted by WallstopProto where protobuf-net writes them explicitly, including enum tuple map keys (#399).
  • Fix string.FromBase64() inventing replacement-character text when the decoded bytes are not valid UTF-8; corrupt payloads now return an empty string instead (#580).
  • Fix entering Play Mode destroying scene-authored RuntimeSingleton components before their scene starts (#582).
  • Fix double allocation when JSON-deserializing arrays: array growth rents from the shared pool and collection property names match without a throwaway string (#504).
  • An AttributeEffect authoring mistake -- Instant with periodic or behaviour data, or an unassigned cosmetic entry -- is now reported once per effect and in the Inspector, not on every application. Each report rendered the whole effect to JSON, measured at 20.5 us (#567).
  • Fix Attribute.CurrentValue reporting the wrong number in the editor: outside play mode it discarded the cached value, so an attribute deserialized while buffed lost the buff, and in play mode an Inspector edit to the base value left the cache stale (#569).
  • Fix a single [SiblingComponent] or [ChildComponent] field binding a disabled component when IncludeInactive = false. With two candidates on one object and the first disabled, the disabled one was assigned instead of the enabled one behind it (#529).
  • Fix a second await of the same AsyncOperation stopping the first one resuming. Two coroutines or tasks awaiting one SceneManager.LoadSceneAsync handle now both continue; previously only the last to register did.
  • Fix one refused protobuf surrogate registration disabling every registration after it, so Vector3, Color and Bounds silently encoded with different bytes. Each is now independent and names the type it could not register.
  • Fix UnityRandom saves and copies losing engine position or pending Gaussian draws. Snapshots preserve sampling caches; restoring also restores Unity's shared random state. See Random Generators (#521, #728).
  • Fix protobuf-net writing a different payload than WallstopProto for a zero-initialized FastVector2Int or FastVector3Int. The surrogate mirrored the cached hash through GetHashCode() rather than the stored field, so the two encoders disagreed on the origin (#309).
  • Fix default(FastVector2Int) and default(FastVector3Int) comparing unequal to the origin they describe. An array element, an unset field or a dictionary miss did not match new FastVector2Int(0, 0), so a set held the origin cell twice. Wire format is unchanged (#309).
  • Fix default(CacheStatistics) and default(PoolStatistics) comparing unequal to an all-zero snapshot, for the same reason (#309).
  • Fix PoolStatistics hashing the three rates it compares with a tolerance, so two snapshots that were equal could hash differently and a set held both (#309).
  • Fix generator metadata that named the wrong algorithm: LinearCongruentialGenerator is the Numerical Recipes ranqd1 LCG, not Park-Miller; RomuDuo matches neither published ROMU duo variant; IllusionFlow is not a PCG or xorshift hybrid (#509).
  • Fix the license recorded for the ROMU algorithm behind RomuDuo: it is Apache 2.0, not CC0 (#509).
  • Fix SortByName() and ScriptableObjectSingleton<T>.Instance throwing on a name whose trailing digits do not fit an int, such as a timestamp, or are not ASCII digits. Such names now order correctly, and a suffix of any length is compared without being parsed (#386).
  • Fix string.Reverse() destroying emoji and other non-BMP characters: it split every surrogate pair, so the result encoded as replacement characters. Reversing twice now returns the original (#386).
  • Fix string.Truncate() returning a result longer than the limit it was given when the ellipsis did not fit, and cutting characters in half. The result now always fits and is always valid text (#386).
  • Fix SerializedStringComparer throwing from Equals and GetHashCode when its serialized mode held an unrecognized value. It falls back to ordinal comparison, and hashes null instead of throwing (#386).
  • Fix fourteen comparables ordering null last instead of first, disagreeing with every IComparable in the framework and with each other. StringWrapper also ordered by hash code rather than by its string (#386).
  • Fix XoroShiroRandom being documented as xoshiro128**. It is xoroshiro128+, whose lowest bits are the weak ones and the half this returns, so its rating is now Good (#285).
  • Fix NextGuid() and NextWGuid() throwing on any generator restored from a protobuf payload read by protobuf-net. Twelve generators were affected, so the first GUID drawn after loading a save crashed (#492).
  • Fix IllusionFlow, PcgRandom, RomuDuo, XorShiftRandom and XoroShiroRandom replaying a different sequence than the one saved when their whole serialized state was at its default (#492).
  • Fix dead or crashing streams when restored shared reservoir, PCG, StormDrop, PhotonSpin or SystemRandom state is invalid. Repair and PhotonSpin warmup now run after construction or deserialization, not on every draw (#492, #503).
  • Fix Helpers.EnumeratePrefabs and EnumerateScriptableObjects searching folders the caller never named when the asset paths were passed as anything other than a string[] (#482).
  • Fix [WShowIf] never matching a member of a ulong-backed enum above long.MaxValue, which hid the field it was meant to reveal (#346).
  • Fix a chosen text colour on a [WButton] or [WEnumToggleButtons] palette entry being overwritten whenever its red, green and blue were all zero, so opaque black was replaced by the auto-computed colour and any transparency was discarded. Existing entries keep the colour they were showing (#476).
  • Fix settings color keys being stored case-sensitively while every reader matched them without regard to case, so "Save" and "save" were two palette entries on disk and one entry to every reader (#476).
  • Fix PositiveMod returning a negative result -- the one thing it promises never to do -- for any maximum above 2^30 (int) or 2^62 (long), and rounding float and double values that were already inside the range. WrappedAdd now wraps the real sum when adding overflows.
  • Fix float and double PositiveMod returning 0 for every input when the maximum is 1, which made the normalized-phase case it documents useless: 5.5f.PositiveMod(1f) is 0.5f. It could also return the maximum itself when the remainder was too small to survive being added to it.
  • Fix SerializableSortedDictionary skipping the rebuild of its serialized arrays when two stored keys compare equal under its own comparer, which could leave a newly added key unsaved (#476).
  • Fix SerializableDictionary, SerializableSortedDictionary and SerializableHashSet writing back entries their own comparer holds as one, so a case-insensitive dictionary could save two entries that merged into one on the next load (#476).
  • Fix SortAlgorithm.Power and SortAlgorithm.PowerPlus reordering elements that compare equal, which both are documented as never doing. Any list whose equal elements sit inside a descending stretch was affected (#461).
  • Fix SerializableDictionary and SerializableSortedDictionary discarding a comparer they were constructed with, so seeding one with StringComparer.OrdinalIgnoreCase — the documented way to make it case-insensitive — did nothing (#472).
  • Fix the settings window treating any color within about two and a half 8-bit steps of the factory default as untouched, so a color you had deliberately changed could be overwritten by the suggested palette (#472).
  • Fix the inspector repainting on every settings check once a custom color held a NaN channel, and missing the removal of a color key present in any snapshot but the first (#472).
  • Fix a log format naming a color Unity cannot parse — $"{value:#notacolor}" — emitting a rich text tag the console shows as literal markup instead of the value. The value is now logged undecorated (#473).
  • Fix LayeredImage discarding one alpha level more than its pixelCutoff asks for at some cutoffs and not others, so it kept different pixels than the sprite tools at the same cutoff (#473).
  • Fix the multi-file selector's list background being white at 15% opacity rather than the opaque dark panel it was written as, and the sprite animation creator's selected-thumbnail border coming back out of gamut and more opaque than its fill. Color * float scales alpha too (#473).
  • Fix [WButton] and the serialized collection drawers labelling buttons with the less readable of black and white on 22.9% of colors. The shipped green "Add" button was at 2.84:1, below the 3:1 large-text floor; it is now 7.40:1 (#471).
  • Fix a dark [WButton] showing no hover or press feedback: darkening a color already near black clamped all three states to the same color (#471).
  • Fix Serializer.ProtoDeserialize() refusing to read back what ProtoSerialize() wrote for a value whose fields are all at their defaults — Vector3.zero, Color.clear, Quaternion(0,0,0,0) and any such contract encode to zero bytes, which was rejected as empty input. A null payload is still refused (#474).
  • Fix TextureScale.Bilinear() and Point() shifting a scaled texture half a texel toward the origin: an upscale never reached the source's brightest pixel and a symmetric image downscaled asymmetrically (#470).
  • Fix TextureScale.Bilinear() and the Image Blur tool pulling a fully transparent pixel's color into its visible neighbors, so red beside transparent green produced a yellow edge. Opaque textures are unchanged (#470).
  • Fix the sprite sheet extractor's preview thumbnails sampling half a texel toward the origin (#470).
  • Fix the WButton and WEnumToggleButtons style and texture caches growing without bound as inspector colours change: two colours the caches called equal were stored under different keys, so every repaint could add another entry (#466).
  • Fix a WButton hover or pressed colour derived from an out-of-gamut palette colour coming back with a negative channel (#466).
  • Fix IRandom.NextColorInRange() returning the varied colour fully opaque, discarding the alpha of the base colour (#466).
  • Fix IRandom.NextColorInRange() throwing ArgumentException for a variance of zero or a negative variance, and returning black for one that is not a finite number (#466).
  • Fix Color.ChangeColorBrightness() returning a colour whose channels are all NaN when the correction factor is NaN (#466).
  • Fix a save holding an unset WGuid failing to load: JSON wrote the empty GUID and then refused to read it back (#437).
  • Fix a JSON payload with min greater than max, or with max missing, crashing a Range<T> load with ArgumentException instead of reporting corrupt data (#437).
  • Fix a Gradient with more than eight colour or alpha keys silently losing the extras and filling the player log with errors. The payload is now refused (#437).
  • Fix Serializer.JsonStringify and JsonSerialize failing on a Type, at the root of a graph or behind an object member, despite the shipped Type converter (#437).
  • Fix an unattributed field joining the wrong group when a type declares more than one [WGroup], and a bare [WGroupEnd] closing a group other than the one it follows. Auto-include now targets the most recently declared group, and a bare end closes every open group (#455).
  • Fix a SerializableDictionary's "Add entry" Value field being drawn 8.5px left of its Key field wherever the inspector indents it (#284).
  • Fix a watcher on a non-GameObject type loading every imported prefab, which is where SendMessage cannot be called during Awake, CheckConsistency, or OnValidate came from. A sub-asset nested inside a .prefab no longer matches a watcher on its type (#280).
  • Fix the asset-change watcher deserializing assets during import to decide whether a path holds a watched type, which produced the same warning. The decision is now made from asset metadata (#439).
  • Fix [DetectAssetChanged] crashing headless editors; it no longer initializes in batch mode (#327).
  • Fix SerializableDictionary<TKey, List<TValue>> and its sorted counterpart saving their keys and none of their values. Dictionaries with other value types keep exactly the same saved data (#314, #348).
  • Report SerializableHashSet<List<TValue>> in the Inspector instead of drawing a column that persists nothing: a list compares by reference, so such a set already treats equal contents as distinct elements (#314, #354).
  • Fix the Inspector drawing an empty value column instead of an error for dictionary values no wrapper repairs, such as List<List<T>> and jagged arrays. Sorted dictionaries are covered too (#357).
  • Fix StartFunctionAsCoroutine() and ExecuteOverTime() stopping forever the first time their action threw. Both now report the failure against the owning object and keep running (#359).
  • Fix disabled logging still evaluating its receiver and building its message, so MySingleton.Instance.Log($"…") created the singleton and allocated in a release build. Define ENABLE_UBERLOGGING to keep logging — see Logging Extensions (#350).
  • Fix GetRandomPointInCircle() and GetRandomPointInSphere() returning points outside the shape far from the origin: at world coordinate 1,000,000 with radius 0.05, half of them were outside.
  • Fix NativePcgRandom producing worse randomness than every other generator: NextFloat() could return exactly 1, NextLong() could return a negative, NextUint(0) threw, and most seeds built a shortened-period stream. Sequences for a given seed have changed (#282).
  • Fix ToCachedName() and ToDisplayName() falling back to a dictionary for any signed enum with a negative member, and sbyte enums allocating a 256-entry cache (#339).
  • Fix EditorCacheHelper.GetEnumDisplayName() rebuilding the enum's value array on every call. Display names are unchanged.
  • Fix [WEnumToggleButtons] throwing OverflowException on an enum with a negative member and taking the Inspector down. The Odin drawer rendered such members as "None" and wrote that back (#339).
  • Fix an interrupted Serializer.WriteToJsonFile() destroying the previous save. Every JSON file write now stages and swaps, and creates missing directories (#319).
  • Fix a copied PooledResource<T> or PooledArray<T> returning the same instance to the pool twice, so two live rentals shared one buffer. Every disposable scope in the package is now disposed at most once, at no allocation cost (#358).
  • Fix a double-disposed AssetDatabaseBatchScope ending an outer scope's batch early, leaving the rest of its asset writes unbatched (#358).
  • Fix WallstopArrayPool<T> and WallstopFastArrayPool<T> allocating 32 bytes on every rent (#367).
  • Fix Color.ToHex() truncating each channel instead of rounding it, so it disagreed with Unity's own ColorUtility.ToHtmlStringRGBA() on half of all colors and could not return FF for any channel below exactly 1 (#466).
  • Fix the sprite cropper and pivot adjuster selecting different pixels than the sheet extractor at the same alpha cutoff, and two transparency scorers disagreeing with their twenty siblings by one alpha level (#466).
  • Fix the Inspector's solid-texture cache holding two entries for one color: colors a hair either side of a channel boundary compared equal and hashed apart (#466).
  • Fix GetAverageColor(ColorAveragingMethod.Dominant) returning a channel above 1 for a saturated color - a dominant white came back as 1.0039 (#466).
  • Fix GetAverageColor(ColorAveragingMethod.Weighted) returning a fully transparent color for opaque black pixels, which weigh nothing under luminance weighting (#466).

[3.5.1] - 2026-07-12

Fixed

  • Unity license lock handoff after CI cleanup: final Unity license returns now emit fail-closed cleanup proof to the organization build lock. Cleanup is bounded and non-masking, failed or cancelled acquisitions still release queued/owned lock state, and only a successful return command or exact allowlisted Unity responses permit the activation slot to enter cooldown instead of quarantine.

[3.5.0] - 2026-07-07

Added

  • Asset change detection loop reset utility: Added AssetChangeDetectionUtility.ResetLoopProtection() so editor tooling can resume [DetectAssetChanged] dispatch after a recursive callback has tripped loop protection, without requiring a domain reload.
  • Feel-good randomness helpers: Added ExactAveragePrd, BadLuckProtection, and WeightedShuffleBag<T> for player-facing randomness that reduces streak frustration while keeping deterministic, testable behavior. ExactAveragePrd solves the pseudo-random distribution coefficient for a configured long-run average, BadLuckProtection implements pity-timer chance ramps and optional hard guarantees, and WeightedShuffleBag<T> emits exact weighted ticket counts before repeating. The helpers expose explicit restore APIs so save/load systems can persist pity/deck state.

Fixed

  • Optional Odin Inspector integration without unguarded Odin assemblies: RuntimeSingleton<T>, ScriptableObjectSingleton<T>, and AttributeEffect now use Odin serialized base classes only when the odininspector package is installed and the package-owned WALLSTOP_UNITY_HELPERS_ODIN_INSPECTOR define is active; otherwise they fall back to Unity base classes. Projects that still define the global ODIN_INSPECTOR symbol without shipping Sirenix assemblies no longer fail package compilation, while Odin projects keep the serialized base behavior automatically.

[3.4.0] - 2026-07-01

See the roadmap for details

Added

  • AssetDatabaseBatchHelper.EnsureAssetFolder / EnsureAssetParentFolder: batch-safe helpers that register every missing folder segment with the AssetDatabase (never via raw disk, which can leave the database out of sync and spawn numbered duplicate folders) and pause any open StartAssetEditing batch first, so a subsequent AssetDatabase.CreateAsset cannot fail with "Parent directory must exist".

Changed

  • [WValueDropDown] provider-misconfiguration is now logged as a Warning, not an Error: a null/empty/missing/throwing or wrong-return-type dropdown value provider is a handled condition — the dropdown falls back to an empty option list — so WValueDropDownAttribute / DropDownValueProvider now log these diagnostics at Warning severity instead of Error. The message text is unchanged. This better reflects that the feature degrades gracefully, and (as a side effect) stops these editor-tooling diagnostics from failing PlayMode tests that incidentally exercise the dropdown path.
  • DirectoryHelper.EnsureDirectoryExists no longer logs an error before throwing: passing a path outside Assets/ in the editor previously emitted a Debug.LogError and threw an ArgumentException (a redundant double signal that polluted the console and could fail unrelated tests that did not expect the log). It now throws the ArgumentException only, and the exception message names the offending path (for example, Cannot create directory 'SomeFolder/SubFolder' outside the Assets folder: AssetDatabase only manages paths under 'Assets/'.). Callers that already catch the exception are unaffected; callers that relied on the console log should read the thrown exception instead.

Fixed

  • Unity 6000.3 compatibility for runtime helpers: Object.GetUnityObjectId, UnityObjectExtensions.FindObjectsOfTypeShim, SceneHelper.EnsureOneComponent, and the Rigidbody2D velocity helpers now gate Unity 6 APIs by the specific version that introduced them instead of treating every Unity 6000.x editor as equivalent. Unity 6000.3 and earlier supported matrix versions compile through the older overloads while Unity 6000.4+ continues to use the newer no-sort object lookup and EntityId.ToULong paths.
  • EffectHandler periodic catch-up after a long frame: missed periodic ticks now advance on their scheduled interval instead of sliding from the current frame, and unlimited periodic effects are capped per update so a large frame hitch cannot monopolize the player loop. Remaining due ticks continue catching up on subsequent updates.
  • Protobuf abstract-root deserialization reporting corrupt data instead of a registration error: abstract [ProtoContract] roots without [ProtoInclude] metadata now require an explicit RegisterProtobufRoot mapping before deserialize, so ambiguous abstract roots fail with the documented type-resolution exception instead of falling through to protobuf-net's version-specific corrupt-data / constructor errors. Explicit registrations also take precedence over abstract root heuristics.
  • Background logging using a stale Unity main-thread capture: WallstopStudiosLogger now shares UnityMainThreadGuard's captured main-thread state, preventing background log calls from being misclassified after test/player lifecycle resets.
  • Protobuf serialization of an EMPTY SerializableHashSet / SerializableSortedSet / SerializableDictionary / SerializableSortedDictionary throwing SerializationInputException ("data is empty") on deserialize: an empty collection serializes to a zero-byte payload (the wrapper carries only repeated fields), but ProtoDeserialize ran its generic empty-payload guard before the collection-wrapper interception, so a valid empty collection could not round-trip. The collection interception now runs first, so an empty collection round-trips to an empty collection; the empty-payload guard still rejects empty input for ordinary (non-collection) message types. Wire format for non-empty payloads is unchanged.
  • Serialization crashing or silently corrupting data on IL2CPP/AOT standalone players: Serializer now serializes the affected types through AOT-safe paths instead of protobuf-net's reflection / Reflection.Emit model, which under IL2CPP hit the unsupported GetTypeModifiers icall or bound immutable structs to empty values. Deque<T>, CyclicBuffer<T>, and SparseSet route through plain array/scalar protobuf wrappers (also fixing a spurious [ProtoAfterDeserialization] ArgumentOutOfRangeException); FastVector2Int, FastVector3Int, ImmutableBitSet, and Parabola route through mutable protobuf surrogates. Serializer.JsonStringify / JsonSerialize of anonymous types (and other types lacking a public parameterless constructor) now use a reflection-light writer instead of System.Text.Json's parameterized-constructor converter, which has no AOT code under IL2CPP. A package link.xml preserves the serialization assemblies. JSON output and protobuf round-trip values are unchanged on mono / editor. The package pre-registers the common element specializations (int, long, float, double, bool, string, Vector2, Vector3, Vector2Int, Vector3Int, FastVector2Int, FastVector3Int); consumers that store a custom value-type element in a Deque<T> or CyclicBuffer<T> must register that T for AOT themselves (reference-type elements share a single generic specialization and need no registration).
  • Relational component attributes ([SiblingComponent] / [ChildComponent] / [ParentComponent]) crashing or resolving the wrong components on IL2CPP/AOT players: the collection fast-path built a runtime generic accessor via Expression.Compile(), which IL2CPP cannot service (it threw at runtime). It now uses the AOT-safe non-generic GetComponents / GetComponentsInChildren / GetComponentsInParent(Type, …) overloads. Include-inactive filtering also wrongly treated every component as enabled on IL2CPP (a reflection delegate path that silently failed), so disabled components were not filtered out; the enabled state is now read AOT-safely. Behavior on mono / editor is unchanged.
  • [WNotNull] validation (this.CheckForNulls()) silently doing nothing in player builds: the null check was compiled only into the editor, so it never threw ArgumentNullException for a null [WNotNull] field in a built player. It now runs in every build (AOT-safe) exactly as the API documents, so missing required references fail fast at runtime instead of being silently ignored.
  • [ValidateAssignment] validation (this.ValidateAssignments()) silently doing nothing in player builds: like CheckForNulls, the warning pass was compiled only into the editor (while the sibling AreAnyAssignmentsInvalid already ran everywhere), so unassigned fields were never reported in a built player. It now runs in every build (AOT-safe), consistent with AreAnyAssignmentsInvalid.
  • ScriptableObjectSingleton assets not regenerating on Unity 6 after the asset file was deleted: when a singleton's .asset was removed but its .meta remained, Unity 6000.3+ retains the path-to-GUID mapping, which ScriptableObjectSingletonCreator treated as "path already occupied" and skipped recreation. Creation decisions now key on the asset body file, so the singleton is recreated on every Unity version.
  • AssetDatabase.CreateAsset failing with "Parent directory must exist" / "Creating asset at path … failed": editor utilities that create assets — ScriptableObjectSingletonCreator, ScriptableObjectSingletonMetadataUtility, AttributeMetadataCacheGenerator, PersistentDirectorySettings, and the sprite/atlas/animation creator windows — now ensure the parent folder is registered with the AssetDatabase before creating the asset, including while an AssetDatabase batch is open (common under -batchmode). Previously, creating an asset in a not-yet-registered folder could fail or be skipped.
  • Unity 6 deprecation warnings from Object.FindObjectsOfType / FindObjectOfType: runtime DI-integration code (Reflex / VContainer / Zenject) now routes object lookups through a version-gated shim that uses FindObjectsByType / FindFirstObjectByType on Unity 2022.2+ (including Unity 6) and the legacy API below it, eliminating the deprecation warnings while preserving behavior.
  • MonoBehaviour.ExecuteFunctionAfterFrame callback never firing in headless/batch mode: the helper yielded WaitForEndOfFrame, which never resumes under -batchmode -nographics (no end-of-frame render signal), so the queued callback was silently dropped on headless players, dedicated servers, and CI. It now advances a single frame in batch mode (the headless-safe equivalent) and continues to use WaitForEndOfFrame in interactive/graphical sessions, so the callback runs in every environment.

[3.3.0]

Added

  • Failed Tests Exporter: New editor utility that hooks into the Unity Test Runner to automatically capture test failures and export them to timestamped text files
    • Automatically records test name, failure message, and stack trace for each failed test
    • Configurable output directory with a visual folder picker — defaults to the project root if not set
    • Path validation on every use: falls back to the project root if the configured directory is missing, invalid, or outside the project
    • Menu items under Tools > Wallstop Studios > Unity Helpers to export or clear captured failures
    • Disabled by default — enable via Project Settings > Wallstop Studios > Unity Helpers

Fixed

  • Editor asset import-loop pressure from unchanged metadata saves: ScriptableObjectSingletonMetadata and AttributeMetadataCache now skip SetDirty / SaveAssets work when regenerated metadata is unchanged. This prevents initialize-on-load and explicit singleton ensure passes from repeatedly re-saving identical assets during Unity test runs or editor refreshes, reducing the chance of Unity's "infinite import loop" detector tripping on native runs.
  • AssetPostprocessorDeferral dedup regressing structurally-equal-but-distinct drains: AssetPostprocessorDeferral.Schedule now dedups pending drains via ReferenceEquals instead of List<Action>.Contains (which invokes Delegate.Equals). The prior structural-equality behavior silently coalesced two distinct delegates that shared the same Method+Target -- common when a local helper returns a lambda that captures only outer-method variables -- so a self-rescheduling drain could be dropped entirely. Matches the documented intent in the AssetPostprocessor safety skill and is pinned by the new ScheduleStructurallyEqualButDistinctDelegatesAreNotDeduplicated regression test
  • WButton conflict warnings hidden in collapsed foldouts: Fixed group conflict warnings (placement, priority, and draw order) not rendering when foldout groups are collapsed. Conflict warnings now render in the group header area regardless of foldout expansion state, and warning cache population is validated across collapsed, expanded, and always-open foldout behavior.
  • Spurious "SendMessage cannot be called..." warnings from asset processors: Eliminated spurious "SendMessage cannot be called during Awake, CheckConsistency, or OnValidate" warnings from DetectAssetChangeProcessor and related editor processors (LlmArtifactCleaner, SpriteLabelProcessor) by deferring callback invocation out of Unity's asset-import phase via a shared AssetPostprocessorDeferral helper backed by EditorApplication.delayCall. The prior synchronous path called AssetDatabase.LoadAllAssetsAtPath / GetComponentsInChildren / user callbacks during the import phase, which triggered Unity's internal sprite/renderer lifecycle relays and produced per-import warning storms. A new opt-out setting (Project Settings > Wallstop Studios > Unity Helpers > Detect Asset Changes, option Defer Post-process Callbacks, default on) restores the old synchronous behavior for users who require it. (#234)
  • Pool auto-purge throttle dropping real purges: Fixed WallstopGenericPool<T>.PurgeInternalCore letting the 1-second MinAutoPurgeIntervalSeconds throttle block MaxPoolSize enforcement. A Rent/Return that advanced _lastAutoPurgeTime on a healthy-pool scan would block a subsequent same-tick call that pushed the pool past MaxPoolSize, silently skipping the CapacityExceeded purge and dropping the matching OnPurge notification. The throttle now has two orthogonal rules: (1) when the pool is observably over MaxPoolSize, the throttle is bypassed entirely so a burst of returns cannot accumulate beyond capacity within a single clock tick; (2) otherwise the throttle still rate-limits scans (preserving the O(1) amortized fast path for healthy pools under contention). The multithreaded variant also guards the timestamp advance with CAS max-semantics so concurrent out-of-order writes cannot regress the throttle clock. Applies to both the multithreaded and SINGLE_THREADED pool variants
  • Pool performance regression from O(n) usage tracking: Fixed RollingHighWaterMark (used by pool purge system) performing O(n) operations on every pool rent/return, causing 100x slowdowns under sustained load. Replaced List<Sample> with CyclicBuffer<Sample> for O(1) add/remove, added incremental running sum for O(1) average computation, and added a monotonic deque for O(1) amortized peak tracking. Previously, 100K rent/return cycles took 20+ seconds; now completes within 200ms budget
  • TexturePlatformOverrideEntryDrawer render-phase mutations: Fixed OnGUI writing to SerializedProperty values every frame without BeginChangeCheck/EndChangeCheck guards. Direct assignments like apply.boolValue = EditorGUI.ToggleLeft(...) and nameProp.stringValue = EditorGUI.TextField(...) dirtied the SerializedObject on every repaint, corrupting undo history. Also removed a redundant render-phase writeback of the computed display label to the platform name property
  • TexturePlatformOverrideEntryDrawer GenericMenu undo and Custom handling: Fixed GenericMenu callback not calling Undo.RecordObjects before mutation and not handling the "Custom" menu option. Selecting "Custom" from the dropdown now correctly sets the platform name to string.Empty (triggering custom mode), and all selections are undoable
  • SourceFolderEntryDrawer render-phase mutation: Fixed EnumFlagsField for selection mode writing to modeProp.intValue every frame without a BeginChangeCheck/EndChangeCheck guard
  • AttributeMetadataCache generator path mismatch: Fixed AttributeMetadataCacheGenerator.GetOrCreateCache() using hardcoded asset paths that did not match the [ScriptableSingletonPath("Wallstop Studios/Unity Helpers")] attribute on AttributeMetadataCache. The generator was creating and loading the cache asset from Assets/Resources/Wallstop Studios/ instead of the correct Assets/Resources/Wallstop Studios/Unity Helpers/ path, causing cache generation to silently fail when the singleton was already loaded at the correct path
  • IntDropDown invalid value handling: Fixed IntDropDownDrawer to properly handle property values that fall outside the configured options. Invalid values (values not in the options array) are now preserved without modification during render and clearly displayed with an "(Invalid)" suffix. Previously, invalid values were displayed as-is without any visual indication. The UI Toolkit IntDropDownSelector.GetDefaultValue() now returns the first option instead of 0
  • Linux dropdown rendering phantom rows: Replaced all EditorGUI.Popup usage with GenericMenu-based dropdowns to eliminate phantom empty rows when selected index is -1 on Linux. Affected drawers: WValueDropDownDrawer, IntDropDownDrawer, StringInListDrawer, TexturePlatformOverrideEntryDrawer (standard variants), and WValueDropDownOdinDrawer, IntDropDownOdinDrawer, StringInListOdinDrawer (Odin Inspector variants). Odin drawers now always use GenericMenu regardless of the page limit setting, since GenericMenu handles all list sizes correctly without the rendering issues that required the threshold (#209)
  • Multi-object editing for WValueDropDown: Added typed SerializedProperty setters for Unity-native types (Vector2, Vector3, Vector4, Color, Rect, Bounds, Quaternion, AnimationCurve, Hash128, and their Int variants) in WValueDropDownDrawer.ApplyOption to avoid the reflection fallback for known property types. The generic reflection path now iterates over all serializedObject.targetObjects for proper multi-object editing support instead of only updating the first selected object
  • SerializableSet undo not working for add, clear, sort, and commit operations: Fixed TryClearSet, TryAddNewElement, TryCommitPendingEntry, AppendNullPlaceholderEntry, and TrySortElements in SerializableSetPropertyDrawer not calling Undo.FlushUndoRecordObjects() after direct object mutation. These methods used Undo.RecordObjects to snapshot pre-change state but never finalized the undo record, causing Undo.PerformUndo() to silently do nothing
  • GUIContent GC pressure in drawer OnGUI: Fixed per-frame GUIContent allocations in IntDropDownDrawer.DrawGenericMenuDropDown and PoolTypeConfigurationDrawer.OnGUI that created avoidable garbage collection pressure in the Inspector. Both drawers now reuse static GUIContent instances (consistent with WValueDropDownDrawer and StringInListDrawer which already followed this pattern)

[3.2.1]

Fixed

  • WValueDropDown empty rows in dropdown: Fixed WValueDropDown and StringInList dropdowns showing empty/blank rows at the top of the dropdown list, particularly on Linux. Clamped invalid -1 selected indices to 0 in WValueDropDownDrawer and StringInListDrawer, and hardened dropdown display logic to replace empty labels with descriptive fallback text (#209)
  • Dropdown display label normalization: Fixed search, filter, suggestion, and selected-value display in WValueDropDown, StringInList, and popup dropdown windows not applying the (Option N) fallback label consistently, causing items with empty display labels to be unsearchable and the wrong option to appear selected (#213)

[3.1.9]

Fixes

  • "Destroying assets is not permitted to avoid data loss" on Asset Domain Reload issue

[3.1.8]

Fixed

  • Unity 6.3 unsigned package warning: Added "signature": "unsigned" field to package.json to explicitly mark the package as unsigned for Unity 6.3+. This prevents Unity from showing a warning that the package is missing a signature. The change is backwards compatible with older Unity versions and works with OpenUPM, npm, and git URL installations.
  • WGroup not working in Unity 6000.x: Fixed WGroup attributes not rendering in Unity 6 by using named parameter syntax for CustomEditor attribute's editorForChildClasses parameter. This change is backward compatible with Unity 2022 and earlier versions.

[3.1.7]

  • DetectAssetChanged scene file crash: Fixed Unity crash ("Do not use ReadObjectThreaded on scene objects!") when .unity or .scenetemplate files were processed by the asset change detection system

[3.1.6]

Fixed

  • Banner SVG Issues: Various issues relating to Unity Helpers banner SVG rendering
  • Documentation nested list rendering: Fixed GitHub Pages rendering where nested bullet lists appeared flat without proper indentation (#175)
  • Documentation too self-congratulatory: Toned down the documentation to be more realistic and less LLM-speak

[3.1.5]

Changed

  • Breaking: Relational component attributes ([SiblingComponent], [ParentComponent], [ChildComponent]) now assign null to single-component fields when no matching component is found and SkipIfAssigned=false (the default). Previously, fields retained their existing values when no component was found. This change makes single-field behavior consistent with collection-field behavior, which already assigned empty collections.

[3.1.4]

Added

  • ScriptableObjectSingletonMetadata Sync Button: Added a Sync button to ScriptableObjectSingletonMetadata inspector that re-scans all assemblies for ScriptableObjectSingleton<T> types and updates their metadata entries. This allows manually refreshing singleton metadata when assets are added, moved, or renamed.

Fixed

  • ScriptableObjectSingletonCreator race condition: Fixed issue where newly created singleton assets were immediately deleted because LoadAssetAtPath returned null before Unity's AssetDatabase had indexed the file. The fix adds a synchronous import after CreateAsset and avoids deleting on-disk files when the file exists but isn't visible to the AssetDatabase yet.

[3.1.3]

Added

  • AnimationCreator Configuration Persistence: Save and load AnimationCreator settings to JSON files alongside sprite source folders.
    • Configurations are automatically saved as .animation-creator.json in sprite source directories
    • Auto-loads existing configurations when source folders are selected
    • Save individual or all configurations with dedicated UI buttons
    • Reset to defaults with optional config file deletion
    • Preserves all settings including animation data, framerate curves, regex patterns, and grouping options
  • AnimationCreator Pagination: Animation data list now uses pagination (20 items per page) for better performance with large animation sets

Fixed

  • AnimationCreator editor performance: Significantly improved scrolling and editing responsiveness when working with animation data.
  • ScriptableObjectSingletonCreator retry exhaustion: Fixed issue where new singleton assets would fail to create with "Maximum automatic retry attempts reached" even when specifying paths.
  • Animation Copier diff detection: Fixed issue where copied animations were incorrectly detected as "changed" instead of "unchanged" after copy operations.

[3.1.2]

Fixed

  • Updated npmignore to align more closely with gitignore. The scripts/tests meta file error when sourcing from npm should be gone.

[3.1.1]

Fixed

  • WInLineEditorOdinDrawer now compiles.

[3.1.0]

Added

  • Pool Access Frequency Tracking: Intelligent purge decisions based on pool usage patterns
  • Memory Pressure Detection: Proactive memory monitoring for intelligent pool purging
  • Cross-Pool Global Memory Budget: Prevents aggregate memory bloat across all pools
  • Size-Aware Purge Policies: Large objects (above LOH threshold) get stricter purge policies
    • WallstopGenericPool<T> automatically uses size-aware options during construction
  • SpriteSheetExtractor: New editor tool for extracting individual sprites from sprite sheet textures.
    • This is an ALPHA feature, much functionality is currently broken.
  • Cache Data Structure: New high-performance, configurable Cache<TKey, TValue> with fluent builder API
    • Multiple eviction policies: LRU, Segmented LRU (SLRU), LFU, FIFO, and Random
    • Time-based expiration with ExpireAfterWrite and ExpireAfterAccess
    • Weight-based sizing for entries of varying cost
    • Dynamic growth with configurable thrash detection
    • Loading cache support with GetOrAdd and custom loader functions
    • Thread-safe by default (single-threaded mode via SINGLE_THREADED define)
    • Eviction, get, and set callbacks for monitoring cache behavior
    • Statistics tracking with hit/miss counts
  • CachePresets: Factory methods for creating pre-configured caches optimized for common gamedev scenarios
  • AnimationCreator Variable Framerate: AnimationCreatorWindow now supports variable framerate animations using AnimationCurve
    • New FramerateMode enum (Constant or Curve) for choosing timing mode
    • Per-animation framesPerSecondCurve allows custom timing across animation progress
    • Curve presets: Flat, Ease In, Ease Out, and Sync with constant FPS
    • Frame timing preview shows per-frame durations before generation
  • AnimationCreator Live Preview: Real-time animation preview panel
    • Play/pause/stop transport controls for preview playback
    • Frame scrubber for manual frame navigation
    • Respects variable framerate curves during preview
    • Shows current frame index and FPS in preview panel
  • AnimationData Cycle Offset: New cycleOffset property (0-1) sets animation loop start point
  • Pool Auto-Purging: WallstopGenericPool<T> now supports configurable auto-purging and eviction
    • New PoolOptions<T> class for configuring pool behavior at construction
    • MaxPoolSize limits pool capacity with automatic eviction of excess items
    • IdleTimeoutSeconds purges items that have been idle too long
    • PurgeTrigger flags control when purging occurs: OnRent, OnReturn, Periodic, or Explicit
    • OnPurge callback with PurgeReason (IdleTimeout, CapacityExceeded, Explicit) for monitoring
    • Intelligent purging mode tracks usage patterns to avoid purge-allocate cycles
    • MinRetainCount ensures a minimum number of items are always kept
  • Application Lifecycle Hooks for Pool Purging: Automatic pool purging in response to system events
    • Application.lowMemory triggers emergency purge (ignores hysteresis, purges to MinRetainCount)
    • Application.focusChanged triggers purge when app backgrounds (mobile platforms)
    • New PurgeReason values: MemoryPressure, AppBackgrounded, SceneUnloaded (reserved)
    • Configurable via PoolPurgeSettings.PurgeOnLowMemory and PoolPurgeSettings.PurgeOnAppBackground
    • GlobalPoolRegistry tracks all pool instances for cross-pool operations
    • PoolPurgeSettings.PurgeAllPools() method for manual global purge
    • Lifecycle hooks automatically registered via RuntimeInitializeOnLoadMethod
  • RandomExtensions NextOfExcept: New extension methods for selecting random elements with exclusions
    • NextOfExcept(values) - no exclusions (convenience overload)
    • NextOfExcept(values, exception1...) - exclude values
    • Zero-allocation using pooled collections internally

Changed

  • BREAKING: Pool purging now enabled by default with conservative settings

    • GlobalEnabled defaults to true (was false)
    • DefaultBufferMultiplier defaults to 2.0 (was 1.5)
    • DefaultHysteresisSeconds defaults to 120 (was 60)
    • DefaultSpikeThresholdMultiplier defaults to 2.5 (was 2.0)
    • Use PoolPurgeSettings.DisableGlobally() to restore previous behavior
    • UnityMainThreadDispatcher auto-load behavior has changed from auto-loading to not auto-loading.
    • UnityMainThreadDispatcher hide flags have been changed to None.
  • DictionaryExtensions ToDictionary: Now uses last-wins semantics for duplicate keys instead of throwing ArgumentException

    • Aligns with common dictionary initialization patterns
    • Applies to both KeyValuePair<K,V> and tuple (K, V) overloads
  • IEnumerableExtensions return types: OrderBy, Ordered, and Shuffled methods now return List<T> instead of IEnumerable<T> for improved usability (indexable, known count)

    • Note: These methods now use eager evaluation (execute immediately) instead of deferred evaluation
    • Source code remains compatible—List<T> is assignable to IEnumerable<T>

Improved

  • LRU cache eviction: Bounded editor caches now use LRU (Least Recently Used) eviction instead of FIFO
    • Frequently-accessed cache entries are retained longer, improving cache hit rates
    • Both reads and writes update an item's "recency", preventing hot items from being evicted
    • Affects EditorCacheHelper.AddToBoundedCache and new TryGetFromBoundedLRUCache method
    • Applied to InLineEditorShared, WShowIfPropertyDrawer, and other bounded editor caches
  • Shuffled performance: IEnumerableExtensions.Shuffled now uses O(n) Fisher-Yates shuffle instead of O(n log n) sort-based approach
  • LINQ elimination: Removed LINQ usage across runtime code for reduced allocations and improved performance
    • Affects Trie, Geometry, Serializer, ValidateAssignmentAttribute, WShowIfAttribute, relational component attributes, and more
    • Uses pooled collections and explicit loops instead of LINQ methods
    • Zero-allocation patterns applied throughout
  • GlobalPoolRegistry.EnforceBudget() zero-allocation: Replaced per-call List<IPoolStatistics> allocation with static reusable list protected by existing lock

Fixed

  • Cache pre-allocation OutOfMemoryException: Fixed production bug where Cache<TKey, TValue> would pre-allocate internal storage to MaximumSize instead of using a small initial capacity
    • Creating a cache with MaximumSize = int.MaxValue now works correctly instead of throwing OutOfMemoryException
    • New InitialCapacity option allows explicit control over starting allocation size (default 16)
    • Cache grows dynamically from InitialCapacity toward MaximumSize as items are added
    • CacheBuilder<TKey, TValue>.InitialCapacity(int) method for fluent configuration
    • Cache<TKey, TValue>.MaximumSize property added to expose configured maximum (distinct from Capacity)
    • Large InitialCapacity values are clamped to MaxReasonableInitialCapacity (65536) to prevent excessive allocations
  • Pool MinRetainCount not respected during gradual explicit purges: Fixed MinRetainCount being ignored when using MaxPurgesPerOperation with explicit purges
    • Gradual purges now correctly stop when pool size reaches MinRetainCount
    • Added _pool.Count > effectiveMinRetain check to the purge loop condition in both thread-safe and non-thread-safe pool implementations
  • Pool idle timeout purges blocked by comfortable size: Fixed idle timeout purges not occurring when pool size was at or below comfortable size
    • Idle timeout purges now proceed regardless of comfortable size, as they represent essential pool hygiene
    • Added hasIdleTimeout to loop entry condition to allow idle timeout evaluation independent of size
  • Pool hysteresis incorrectly blocking idle timeout purges: Fixed hysteresis protection blocking all purge types including idle timeout
    • Idle timeout purges now proceed during hysteresis since they only remove items unused for extended periods
    • Capacity and explicit purges remain blocked during hysteresis to prevent thrashing
  • ScriptableObjectSingletonCreator race condition creating numbered duplicate folders: Fixed race condition where parallel operations could cause Unity to create numbered duplicate folders like "Resources 1", "Resources 2", etc.
    • Added detection for Unity's numbered duplicate folder creation pattern
    • Automatically deletes duplicate folders and uses the intended folder path
    • Logs warning if duplicate folder deletion fails, alerting user to manual cleanup needed

[3.0.5]

Added

  • GitHub Pages Support: All documentation is now available via a pretty GitHub Pages
  • GitHub Wiki Support: All documentation is now available via a less pretty GitHub Wiki
  • Comprehensive Odin Inspector Attribute Support: All Unity Helpers inspector attributes now work seamlessly with Odin Inspector's SerializedMonoBehaviour and SerializedScriptableObject types
    • [WButton]: Full support including grouping, placement, history, async methods, and parameters
    • [WShowIf]: Conditional property display based on field values, methods, or comparisons
    • [WReadOnly]: Disables editing while preserving display in Odin inspectors
    • [WEnumToggleButtons]: Toggle button UI for enum selection with flags support
    • [WValueDropDown]: Dropdown selection from custom value lists
    • [WInLineEditor]: Inline editing of referenced ScriptableObjects and components
    • [WNotNull]: Null reference validation with HelpBox warnings/errors
    • [ValidateAssignment]: Field validation for null, empty strings, and empty collections
    • [StringInList]: String selection from predefined lists or method providers
    • [IntDropDown]: Integer selection from predefined value lists
    • No setup required — attributes work identically whether Odin Inspector is installed or not
    • Custom Odin drawers registered when ODIN_INSPECTOR symbol is defined
  • WButton Custom Editor Integration: New WButtonEditorHelper class for integrating WButton functionality into custom editors
    • Only needed when creating custom OdinEditor subclasses for specific types
    • Provides simple API for any custom editor to draw WButton methods
    • Methods: DrawButtonsAtTop(), DrawButtonsAtBottom(), ProcessInvocations(), and convenience methods
    • Documented integration patterns for both Odin Inspector and standard Unity custom editors

Fixed

  • Sprite Sheet Auto-Detection Preferring Non-Transparent Boundaries: Fixed an issue where the "Auto Best" algorithm and other detection methods could select grid boundaries that pass through non-transparent pixels when transparent alternatives existed
    • Changed scoring system from linear to non-linear, heavily favoring fully transparent grid lines (10x higher score) over partially transparent ones
    • Adjusted boundary comparison to only prefer alternatives when transparency score differs by more than 5%, preventing minor variations from overriding better transparent boundaries
    • When scores are similar, the algorithm now prefers divisors closer to the originally detected cell size
    • This fix affects ScoreDivisorByTransparency, ScoreCellSizeForDimension, and FindBestTransparencyAlignedDivisor methods
  • Manual Recompile Silent Failure After Build: Fixed an issue where the "Request Script Recompilation" menu item and shortcut would stop responding after building a project (particularly on Linux)
    • Added defensive null check in compilation pending evaluator to prevent silent NullReferenceException
    • The null evaluator scenario could occur when static field initialization failed or was corrupted during build operations without a domain reload

[3.0.4]

Fixed

  • Documentation only (WGroupEnd examples)

[3.0.3]

Fixed

  • Fix packaging issue related to rsp files

[3.0.2]

Fixed

  • Fix packaging issue related to Styles/Elements/Progress.meta file

[3.0.1]

Fixed

  • Updated package.json to be OpenUPM-compatible

[3.0.0]

Added

  • llms.txt: Added llms.txt file following the llmstxt.org specification for LLM-friendly documentation
    • Provides a structured overview of package features, APIs, and documentation links for AI assistants
    • Enables third-party LLMs to quickly understand and work with the Unity Helpers codebase
  • Auto-Load Singleton System: New singleton pattern with configurable lifetimes and thread-safe execution
    • UnityMainThreadGuard for ensuring operations run on the main thread
    • UnityMainThreadDispatcher with configurable lifecycle management
    • AutoLoadSingletonAttribute for automatic singleton instantiation during Unity start-up phases
    • Reworked the autoload singleton architecture for better scene persistence
  • Asset Change Detection: Monitor asset changes with DetectAssetChangedAttribute
    • Annotate methods to automatically execute when specific asset types are created or deleted
    • Support for inheritance with IncludeAssignableTypes option
    • Automatic registration and callback execution via asset processor
  • Inspector Attributes & Drawers: Comprehensive custom inspector tooling
    • WGroup attribute for visual grouping of inspector properties, including collapsible sections and palette-driven styling
    • WButton attribute with support for async/Task methods and custom styling
    • WEnumToggleButtons attribute for toggle-based enum selection in inspector
    • WShowIf conditional display attribute improvements
    • Enhanced dropdown attributes for better property selection
  • Inspector Validation Attributes: Enhanced inspector feedback for null/invalid field detection
    • WNotNullAttribute now displays a warning or error HelpBox in the inspector when the field is null
    • WNotNullAttribute new properties: MessageType (Warning/Error enum) and CustomMessage (string) for customizable feedback
    • WNotNullAttribute new constructor overloads for easy customization of message type and custom messages
    • New WNotNullPropertyDrawer for rendering validation feedback in the inspector
    • ValidateAssignmentAttribute now displays a warning or error HelpBox in the inspector when the field is invalid (null, empty string, or empty collection)
    • ValidateAssignmentAttribute new properties: MessageType (Warning/Error enum) and CustomMessage (string) for customizable feedback
    • ValidateAssignmentAttribute new constructor overloads for easy customization of message type and custom messages
    • New ValidateAssignmentPropertyDrawer for rendering validation feedback in the inspector
    • Both attributes maintain full backward compatibility—existing code works unchanged with default warning messages
    • StringInListAttribute now supports [StringInList(nameof(Method))] to call parameterless instance or static methods on the decorated object, and the drawer exposes the same experience in both IMGUI and UI Toolkit inspectors
    • WButton now supports groupPriority and groupPlacement parameters for fine-grained control over button group ordering and positioning
  • Serialization Data Structures: Production-ready serializable collections
    • SerializableDictionary<TKey, TValue> with custom inspector drawer
    • SerializableSortedDictionary<TKey, TValue> with ordered iteration
    • SerializableHashSet<T> with custom set drawer and duplicate detection
    • SerializableSortedSet<T> for sorted sets with IComparable<T> elements
    • SerializableNullable<T> for nullable value types in inspector
    • SerializableType for type references in inspector
    • Pagination support for large collections in the Editor
    • Inline nested editor support for complex types
    • Undo/Redo support for all serializable collection modifications
    • Confirmation dialog when clearing collections to prevent accidental data loss
  • Editor Tooling Enhancements:
    • Enhanced StringInListDrawer for validated string input with suggestions
    • UI Toolkit-based editors for modern Unity editor integration
    • Configurable settings windows with improved layout and styling
    • Move up/down buttons for reordering collection elements
    • Add/remove buttons with improved visual styling
    • Added Request Script Recompilation menu item (Tools ▸ Wallstop Studios ▸ Unity Helpers) to manually trigger script recompilation
    • The "Request Script Compilation" utility includes a Unity Shortcut Manager binding (default Ctrl/Cmd + Alt + R) for quick access. The shortcut appears under Wallstop Studios / Request Script Compilation and can be remapped like any other Unity shortcut.
    • Coroutine wait buffer defaults can now be configured under Project Settings ▸ Wallstop Studios ▸ Unity Helpers. The generated Resources/WallstopStudios/UnityHelpers/UnityHelpersBufferSettings.asset applies the selected quantization, entry caps, and LRU mode automatically on domain reload or when the player starts (unless your code overrides the values at runtime).
    • Added Unity Method Analyzer (Tools ▸ Wallstop Studios ▸ Unity Helpers ▸ Unity Method Analyzer) for detecting inheritance issues and Unity lifecycle method errors across C# codebases
  • Random Number Generation: Extended PRNG capabilities
    • Added BlastCircuitRandom and WaveSplatRandom generators with improved performance characteristics
    • New RandomGeneratorMetadata system for inspecting generator properties
    • Extended random sampling methods with improved statistical distribution
  • Array Pooling: New SystemArrayPool<T> and unified PooledArray<T> return type
    • Added SystemArrayPool<T> wrapping System.Buffers.ArrayPool<T>.Shared for variable-sized allocations
    • Added PooledArray<T> struct as unified return type for all array pools with proper Length tracking
    • WallstopArrayPool<T> and WallstopFastArrayPool<T> now return PooledArray<T> instead of PooledResource<T[]>
    • Critical for SystemArrayPool<T>: returned arrays may be larger than requested; always use pooled.Length, not array.Length
  • Grid Concave Hull Reliability:
    • Edge-split and grid KNN hull builders now insert missing axis-aligned corners after the initial pass, guaranteeing concave stair, horseshoe, and serpentine inputs retain their interior vertices even when only sparse samples exist.
    • Improved handling of staircase patterns, axis-corner preservation, and diagonal-only rejection for more robust hull generation.

Fixed

  • Random Number Generation: Critical edge case handling
    • Fixed poor handling of NextFloat() and NextDouble() potentially returning exactly 0.0 or 1.0 in extensions and helpers
    • Fixed sampling bias in NextUlong() for more uniform distribution
    • Ensured proper range handling for all random generation methods
  • IllusionFlow Random: Serialization and performance issues
    • Fixed deserialization bugs in IllusionFlow components
    • Optimized to reduce GC churn during effect processing
  • Editor & Inspector: Multiple rendering and caching bugs
    • Fixed stale label caching causing incorrect inspector display
    • Fixed scene loading edge cases in editor workflows
  • Component System: Runtime component query issues
    • Fixed GetComponents returning null arrays in some cases
    • Fixed jitter-related bugs in component updates
  • Extension Methods: Mathematical edge cases
    • Fixed calculations with zero or negative areas (bounds, rectangles, circles)
    • Fixed color averaging bugs in color extension methods
  • Geometry & Spatial: Convex hull computation
    • Fixed convex hull behavior for edge cases (collinear points, degenerate cases)
    • Improved hull computation accuracy and performance
  • GUID Generation: Specification compliance
    • Fixed GUID v4 generation to properly set version and variant bits per RFC 4122
  • Editor Settings: Project settings and drawer issues
    • Fixed obsolete API usage in editor code
    • Fixed project settings panel rendering issues
    • Fixed reflection-based property access for better performance
  • Scriptable Object Singletons: Duplicate folders should no longer be created
    • Fixed a "should-never-happen" bug where, if a singleton was accessed for the first time off the main thread, it would never be able to be accessed for the lifetime of the process
    • Fixed a bug where auto-creation would happen concurrently with AssetDatabase importing, resulting in Unity crashing with no error message

Improved

  • Performance Optimizations:
    • Reduced reflection usage in custom property drawers (10-100x faster in some cases)
    • Optimized list navigation and caching for large collections
    • Faster indexing and lookup in serializable data structures
    • Improved drawer update performance for complex inspector hierarchies
    • Data structure conversion optimizations
    • Minor relational component performance improvements, specifically for children components
    • Reduced GC allocations across property drawers, editor tools, and various helpers
  • EnhancedImage Visual Component:
    • Improved material instance management with proper cleanup OnDestroy
    • Better domain reload handling for HDR color and material state persistence
    • Enhanced editor inspector with automatic material fix suggestions
  • Animation Editor Tools:
    • Fixed FPS field handling in Animation Viewer and Sprite Sheet Animation Creator
    • Improved frame reordering and preview responsiveness
  • Documentation:
    • Major documentation refactor for clarity
    • Added GUID generation documentation
    • Improved inline code documentation
    • Better attribute usage examples

Changed

  • Breaking Changes:
    • Removed KVector2 (deprecated, use Unity's built-in Vector2)
    • Renamed KGuid -> WGuid, changed data layout
    • Forced WallstopFastArrayPool to force unmanaged types. This pool does not clear arrays and can leak references.
    • WallstopArrayPool<T> and WallstopFastArrayPool<T> now return PooledArray<T> instead of PooledResource<T[]>. Update usages from pooled.resource to pooled.Array and consider using pooled.Length for iteration bounds.
    • The legacy line-division concave hull overload BuildConcaveHull(IEnumerable<FastVector3Int>, Grid, float scaleFactor, float concavity) has been marked [Obsolete] and now throws NotSupportedException. Use ConcaveHullStrategy.Knn or ConcaveHullStrategy.EdgeSplit (and their dedicated helpers) instead; the docs now call out this retirement explicitly.
    • StringInList inspectors now keep the property row single-line and open a dedicated popup that contains search, pagination, and keyboard navigation for large catalogs (applies to both IMGUI and UI Toolkit drawers, including SerializableType).
  • API Improvements:
    • Simplified TryAdd methods for collections
    • Enforced IComparable constraint where appropriate for sorting
    • Better handling of null additions in collections
    • Updated editor tooling for better integration with Unity 2021.3+
    • Default IList.Sort to Grail sort for stability and improved performance
  • Documentation:
    • Updated documentation to reflect new features and API changes
    • Re-organized documentation into a more logical structure
    • Consolidated documentation naming around kebab-case

[2.0.0]

  • Deprecate BinaryFormatter with [Obsolete], keep functional for trusted/legacy scenarios.
  • Make GameObject JSON converter output structured JSON with name, type, and instanceId.
  • Fix stray UnityEditor imports in Runtime to ensure clean player builds.

[1.x]

  • See commit history for incremental features (random engines, spatial trees, serialization converters, editor tools).
⚠️ **GitHub.com Fallback** ⚠️