Skip to content

3.6.0

Latest

Choose a tag to compare

@github-actions github-actions released this 12 Sep 19:19
· 23 commits to main since this release
08de791

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 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).

Install

  • Import the attached .unitypackage into a Unity project, or
  • install com.wallstop-studios.unity-helpers@3.6.0 from npm / OpenUPM through Unity Package Manager.

The release includes the npm tarball and the .unitypackage, each with a .sha256 checksum.