Skip to content

Latest commit

 

History

History
560 lines (458 loc) · 34 KB

File metadata and controls

560 lines (458 loc) · 34 KB

Unity finish scope and size audit

2026-09-21. The Unity work is complete at implementation commit a7fff23. This record supersedes the open-ended sequencing and stale checklist in the original research plan, which remains the source of the ASR comparison and implementation evidence.

Finish line

Ship the researched ASR profiles and runtime improvements, plus the requested recursive String/array/List/Map/Set reads and owned class snapshots. Unused features must not add their discovery/readers to Wasm. Lunistice Release finishes at 32,121 bytes with an explicit profile and 142,234 bytes with automatic selection, preserving the existing source, output pipeline, and base/DLC behavior. The user accepted stopping when no further concrete optimization saves roughly 500 bytes or more; this replaces the earlier below-30,000-byte completion gate. Breaking internal changes are fine.

Do not add further features or expand malformed-metadata coverage without a concrete defect that prevents these requirements from working. Raw UTF-16, migration support, and unrelated compiler optimization are outside this work.

What explains the size

These are stored measurements, inspected without rebuilding:

Revision Explicit Lunistice bytes Change
001c38a 23,141 Before IL2CPP profile migration
52f5b65 76,166 Profile migration and source-defined metadata discovery
d5cc416 74,926 Nested-name/generic metadata changes
8590627 56,641 Shared field traversal
8a759ae 60,345 Implementation at the start of this audit
Before class-scan change 57,399 Pending discovery consolidation
57ed738 49,437 Bounded synchronous class scan
7545e6f 43,627 Bounded image scan and fixed-width discovery
c4b0e39 41,504 Debug-only descriptor sanity checks
c5089e5 40,194 Direct field binding
2c0dc00 38,995 Smaller lookup continuations
36c30ef 36,922 Remove unused linear-memory string copies
c46a76e 36,136 Unqualified schema class matching
4473e70 34,436 Passive GC string initializers
eedc9ff 33,234 Grouped required-field binding
bb9485a 32,353 Name-only lookup diagnostics
a7fff23 32,121 Shared required/conditional binding; final retained implementation

Historical evidence: tests/baselines/unity.json at each revision. The tracked baseline at a7fff23 records the final accepted measurements.

The major regression happened during the profile migration, before the nested collection work. At 52f5b65, field lookup alone occupied 21,166 bytes and class lookup 14,611 bytes. The migration replaced compact compiler-generated readers with source-defined async discovery. The generated implementation, rather than just the number of profile entries, made this expensive.

At the start of this audit, explicit output contained 50,604 bytes of code, 2,838 bytes of data, and 3,957 bytes of other sections/framing. Its largest function bodies included:

Function Bytes
Class discovery poll 9,750
Selected provider preparation poll 4,453
Image discovery poll 3,173
IL2CPP global discovery poll 2,292
Shared field traversal step 2,284
Profile validation 1,941
Shared field traversal poll 1,622

These were absolute costs, not individually proven removable savings. They did not attribute every byte of growth. That output also retained a 1,020-byte 32-bit table-discovery poll despite selecting a fixed x64 profile. This and runtime validation of a built-in constant profile became specialization targets, addressed in the historical checkpoints below.

The old IL2CPP year/version API is already removed. Mono's V1/V1Cattrs/V2/V3 families remain in current ASR, and no Mono functions appear in the explicit Lunistice report. That report contains the selected profile factory and no automatic profile-selection catalog. Removing legacy version tables is therefore not a pending fix for this artifact.

Completion status

The finite audit below maps all 14 researched ASR PRs and the additional nested managed-value/snapshot requirements to implementations and fixtures. No requested feature remains missing. Imported profiles, unused-feature exclusion, recursive reads, snapshot failure behavior, and Lunistice editions have passed verification. The final retained source passes 690 compiler tests, 210 public runtime scenarios across 172 validated artifacts, documentation validation, formatting, and Clippy. The private adapter checks and their resource-limited execution are recorded below.

The final size remains above the original target. A subsequent width-reader experiment saved only 109 bytes; another candidate shared a 321-byte scan body. Both experiments were discarded under the user's stopping criterion. There is no demonstrated remaining saving above roughly 500 bytes to pursue within this scope. General Release compiler optimization and explicit field/property syntax remain separate TODO items, not completion dependencies.

All builds/tests use the serialized scripts/run_limited.py resource guard. No game was launched for closeout. Native Linux/macOS live-game validation remains unavailable; platform fixtures and Lunistice's live title-screen attachment are not claims of full live playthrough validation.

The sections below retain the investigation history. Their intermediate size targets and “Remaining” notes describe those checkpoints, not current blockers.

First size correction

IL2CPP class lookup now performs at most 64 slots in a synchronous step, with a small async wrapper retaining only the scan cursor. Failed batches retry without committing partial progress. Null slots, delayed initialization, ambiguity, negative starts, count limits, nested matching, yielding, and cancellation retain their existing behavior. This follows the existing field cursor approach.

The isolated change reduces explicit Lunistice from 57,399 to 49,437 bytes (-7,962), and automatic selection from 179,728 to 171,009 bytes (-8,719). The class step is 1,140 bytes; the former class poll was 9,750 bytes. Combined with the previously pending checked-name/image consolidation, explicit output shrinks 10,908 bytes relative to the committed baseline. It is still 19,438 bytes above the maximum permitted by the below-30,000-byte requirement.

All 33 metadata artifacts validate and all 38 associated runtime scenarios pass, including 98 bounds/retry cases in each build profile, profile selection, nested names, inherited fields, and cancellation. Both Lunistice editions pass. All 38 baseline artifacts shrink or remain the same size; native, local Map/Set, and standalone identity/export fixtures retain identical sizes. Runtime helper sets, scratch requirements, and initial memory pages are unchanged. The small increases in function/type counts represent the cursor, its result, and synchronous helpers replacing the much larger async routine; they do not reverse the byte savings.

The 48 compiler profile/code-generation tests and documentation validation also pass under the resource guard.

The next size work remains discovery/preparation and specialization of known profile facts. This change does not establish final parity or final size acceptance.

Image scanning and fixed profile widths

IL2CPP image discovery now scans at most 64 assembly slots synchronously and keeps a small async continuation. A failed batch retries without losing earlier completed batches. The fixtures cover large vectors and repair in a later batch at both pointer widths, in addition to the existing initialization/error cases.

Generated state preparation selects the matching discovery root when a literal profile or zero-argument constructor chain exposes its pointer width. Built-in profiles and ordinary custom descriptors use this path. More complex constant expressions conservatively retain generic discovery. Profile and target-width validation still run. Automatic selection and ordinary dynamic calls continue to support both architectures. This is a bounded specialization in existing Unity provider preparation, not a general compiler optimization pass.

Explicit Lunistice shrinks 49,437 -> 43,627 bytes (-5,810), with both editions passing. Its x86 discovery functions are absent. The x86 fixture likewise omits the x64 discovery routine and relative-target scan helper. Automatic Lunistice shrinks 171,009 -> 170,339 bytes (-670). Automatic output retains separate architecture routines and their types; the image-scan savings exceed that cost. Every one of the 38 baseline sources is unchanged, no fixture grows, and no new runtime helper, scratch capacity, or initial memory page is retained.

All 49 profile/code-generation tests pass, including built-in x86/x64 profiles, constructor aliases, custom descriptors, and opposite-architecture exclusion. All 33 metadata artifacts validate and all 38 runtime scenarios pass. The bounds fixture now has 102 cases per build profile with a maximum of 259 reads in one update, including the new image-batch retry cases.

The 56 catalog/type-checking tests, Clippy with warnings denied, and documentation validation also pass under the resource guard.

The explicit artifact remains 13,628 bytes above the largest size permitted by the completion requirement. The original parity audit and final size acceptance remain open; this checkpoint does not add another feature requirement.

Debug-only descriptor sanity checks

Following the user's request, IL2CPP descriptor alignment, offset, and stride sanity checks now use the existing debug statement. Debug still rejects the malformed custom-profile fixture before discovery. Release omits the validator for both measured and custom profiles. Target pointer-width validation remains in both build profiles, as do checked reads, bounded traversal, and snapshot failure behavior. No separate trusted-profile API or compiler routing was added.

Explicit Lunistice shrinks 43,627 -> 41,504 bytes (-2,123), with both editions passing. Automatic selection remains 170,339 bytes. All 38 size fixtures shrink or stay unchanged; runtime helper sets, scratch capacity, and memory pages are unchanged. The 49 profile/code-generation tests pass, including Debug retention and Release exclusion of descriptor validation. All five relevant runtime artifacts validate and pass 56 cases, covering measured profiles, custom profiles, wrong target widths, reattachment, and malformed descriptors in Debug. Documentation validation passes. Builds and tests ran through the resource guard.

Final size acceptance remains open: 11,505 bytes must still be removed to reach an artifact strictly below 30,000 bytes. The parity audit also remains open.

Finite parity audit: implementation and coverage

The review uses ASR commit cf732d3aeac7509c8ab5f29a4d0d28f16487d245 in a separate local checkout; the user's ASR branch and changes remain untouched. Both profile importers pass --check against that commit: 26 PE Mono profiles and eight fallbacks, 13 ELF profiles and four fallbacks, two Mach-O profiles and four fallbacks, and 22 complete IL2CPP profiles. These checks compare the source library and checked-in fixture catalogs, not just profile counts.

Requirement Current implementation Maintained behavior evidence
#142 binary identities and testing Module.peDebugId, elfBuildId, machUuid; synthetic host and mapped-image fixtures pe_debug_id_runtime.mjs, unix_identity_runtime.mjs
#143 Windows Mono profiles Imported GUID+age/width descriptors, old and modern module discovery, assembly/image name routes mono_profiles_runtime.mjs
#144 IL2CPP layouts and discovery Full descriptors, x86/x64 scans, inline/handle starts, bounded module windows il2cpp_profiles_runtime.mjs, unity_metadata_bounds_runtime.mjs; intermediate version selection superseded by #160
#145 shared traversal UnityClassNames, UnityFields, backend cursors, declaring-owner propagation unity_field_cursor_runtime.mjs, mono_metadata_cursor_runtime.mjs, unity_metadata_bounds_runtime.mjs
#146 nested/generic metadata and statics Declaring-chain names, Mono generic-definition counts, IL2CPP sentinel count, owner-relative statics unity_nested_metadata_runtime.mjs, mono_generic_metadata_runtime.mjs, managed_inherited_static_runtime.mjs, mono_static_path_runtime.mjs
#147 managed strings and value arrays Width-aware object headers, stored lengths, shared managed decoder graph managed_string_width_runtime.mjs, managed_arrays_runtime.mjs; raw UTF-16 intentionally excluded
#148 lists Runtime ancestor/layout discovery, actual size versus backing capacity, attachment-scoped cache managed_list_layout_runtime.mjs, managed_lists_runtime.mjs
#150 dictionaries Validated entry metadata and live-slot scan, recursive local Map construction managed_keyed_layout_runtime.mjs, managed_keyed_slots_runtime.mjs, managed_maps_runtime.mjs
#151 hash sets Slot layout/high-water scan and recursive local Set construction The shared keyed fixtures plus managed_sets_runtime.mjs
#152 old corlib parallel arrays Separate links/keys/values or slots, either link-member order, hash-high-bit liveness Parallel modes in the keyed, map, and set fixtures
#153 reference arrays/lists and direct strings Pointer-width element slots, nullable children, recursive string/object decoders Array/list/string fixtures and managed_deep_snapshot_runtime.mjs
#155 Linux Mono Runtime-ID precedence, measured/fallback layouts, ELF symbol and x64 discovery mono_linux_runtime.mjs, mono_explicit_unix_runtime.mjs, elf_export_runtime.mjs
#156 macOS Mono Active-slice UUID/architecture, x86_64 and ARM64 discovery, measured/fallback layouts mono_mac_runtime.mjs, mono_explicit_unix_runtime.mjs, mach_export_runtime.mjs
#160 final IL2CPP selection Complete explicit/custom profiles; exact four-part version then major/minor fallback; removed year selectors Profile importer, il2cpp_profiles_runtime.mjs, compiler profiles_codegen tests

The internal list/keyed layout and IL2CPP storage fixtures run through src/managed_collection_layout_tests.rs; public schema fixtures run through the xtask runtime catalog. Both must be included in final verification.

Beyond ASR, src/managed_read.rs models fixed memory values, strings, arrays, lists, maps, sets, classes, and nullable children as composable decoder nodes. Compiler capability tests cover the MemoryReadable implication and generic constraints. Public fixtures exercise maps of string arrays, arrays of maps, nested keyed collections, lists of class snapshots, recursive class trees, nullability, duplicate decoded keys, and transitive freezing. They also check that a failed nested read preserves current and old. The compiler's unused schema tests compare actual module/section sizes, helpers, data bounds, and scratch requirements, rather than relying only on retained symbol names.

The runtime and Lunistice port transparently recognize property backing fields, as ASR does. Conflicting exact-name documentation and an AST comment have been corrected to describe that behavior. The user prefers explicit field/property intent in the future; the syntax decision is deferred in TODO.md. The experimental exact-name change and its Lunistice source edits were reverted.

A stale standard-library paragraph also claimed IL2CPP generic storage was unimplemented; it now describes the existing implementation.

No additional requested feature is identified by this review. Final verification and size acceptance are recorded in the completion status above. Native Linux/macOS game validation is unavailable here and remains explicitly unverified; the platform evidence above is synthetic. Lunistice's recorded live evidence covers title-screen attachment, while maintained fixtures cover base/DLC behavior. The separate P1 compiler optimization project in TODO.md is not a dependency.

Direct field binding

Removed redundant IL2CPP forwarding futures and the obsolete !0 ambiguity sentinel check: the shared field cursor already rejects ambiguous metadata. Checks, rejection messages, backing-field matching, retry, cancellation, and declaring-owner static storage remain in place. Lunistice source is unchanged.

Explicit Release shrinks 41,504 -> 40,194 bytes (-1,310); automatic selection shrinks 170,339 -> 168,934 bytes (-1,405). All 38 baseline artifacts shrink or stay unchanged, with the same source fingerprints, runtime helpers, scratch capacities, and initial page counts. The 33 metadata artifacts and 38 associated runtime scenarios passed for this isolated change, as did both Release Lunistice editions. Evidence is in target/unity-direct-fields-*.log.

The retained wrapper-only patch was rebuilt and the 33-artifact / 38-scenario metadata matrix passed again. All 38 baseline sources are unchanged, both Release Lunistice editions pass, and the reviewed baseline passes its strict gate. Final evidence is in target/unity-direct-fields-final-*.log. The broader exact-alias experimental test run is not used as verification of this retained patch. General compiler optimizations remain deferred.

Remaining: remove another 10,195 bytes to get strictly below 30,000, then run the final repository checks. The finite functional audit found no further missing requested features. No game was launched.

Smaller lookup continuations

Moved field-name expansion into a synchronous helper, removed the image lookup forwarding future, and merged required-class validation into the class scan continuation. Static-slot lookup retries its table read and checked offset together. Matching, ambiguity, missing-class behavior, and cancellation are preserved. These are library changes to the discovery routines introduced by the Unity migration; general compiler optimizations remain deferred.

Explicit Lunistice shrinks 40,194 -> 38,995 bytes (-1,199); automatic selection shrinks 168,934 -> 167,631 bytes (-1,303). All 38 baseline artifacts shrink or stay unchanged, with identical runtime helper sets, scratch capacities, read capacities, and initial page counts. The static-slot retry adds a 36-byte error literal while removing substantially more continuation code and types. Nine unaffected artifacts retain identical normalized function bodies and sections. The Lunistice source fingerprint changes only because of a line wrap; compiling the prior source with the current compiler produces byte-identical Release Wasm.

All 33 metadata artifacts and 38 runtime scenarios pass, both Release Lunistice editions pass, and documentation validation passes. Logs are target/unity-lookup-continuations-*.log.

Remaining: remove another 8,996 bytes to get strictly below 30,000, then complete final repository verification. No game was launched.

Remove unused linear-memory string copies

The static-data planner interned every reachable string expression, although ordinary strings are constructed as GC arrays. This also retained text from Unity metadata errors whose payload construction was already omitted. Static strings now follow their actual ABI consumers: process and module names, settings, pointer paths, and attachment-shape reports. GC string construction, error behavior, signature data, and the output pipeline are unchanged. This fixes demand-driven emission; it does not introduce the deferred general optimization passes or the earlier string-pooling experiment.

Explicit Lunistice shrinks 38,995 -> 36,922 bytes (-2,073); automatic selection shrinks 167,631 -> 159,829 bytes (-7,802). All 38 artifacts shrink or remain unchanged. Source fingerprints, function-body byte counts, type/function counts, helper sets, scratch capacity, and initial pages are identical. Both Release Lunistice editions pass.

Reachability tests now inspect GC byte-array instructions as well as literal bytes. New checks cover retained module-query data, omitted GC/error copies, and large strings that do or do not require linear memory. The compiler run passed 684 tests; its sole failure was an outdated page-count expectation in the new settings case. After correcting that expectation, all 50 profile tests and the targeted signature-data test pass.

The full public runtime catalog compiled and validated 170 artifacts and passed 207 of 208 scenarios. The remaining smoke fixture supplied a GameManager object without its IL2CPP class header, so the existing nominal-class check rejected it. Adding the missing header makes that scenario pass as well. No runtime behavior was changed to accommodate the fixture. Logs: target/unity-static-data-*.log.

The strict regression baseline, documentation validation, Rustfmt, and Clippy with warnings denied pass under the resource guard.

Remaining: remove another 6,923 bytes to get strictly below 30,000, then complete final repository verification. No game was launched.

Unqualified schema class matching

Generated binding selects a simple comparison when every candidate for that class lacks namespace and declaring-class qualifiers. Qualified aliases and namespace declarations keep the flat matcher; nested names keep declaring-chain matching. The decision is per class, so an unused qualified declaration cannot force a parser into an otherwise simple script. Runtime ambiguity checks and unqualified names' namespace-agnostic meaning are preserved.

Explicit Lunistice shrinks 36,922 -> 36,136 bytes (-786); automatic selection shrinks 159,829 -> 158,930 bytes (-899). Explicit output no longer retains StringRFind or StringSlice for class matching. None of the 38 baseline artifacts grows; source fingerprints, scratch/read capacities, and initial page counts are unchanged, and no new runtime helper is retained.

All 50 profile/code-generation tests pass, including qualified, mixed-alias, namespace, nested, and unused-declaration cases. All 33 metadata artifacts and 38 runtime scenarios pass. The bounds fixture now has 108 cases per build profile, including unqualified names in nonempty runtime namespaces at both pointer widths. Both Release Lunistice editions, documentation, Rustfmt, and Clippy with warnings denied pass. Logs: target/unity-unqualified-*.log.

Remaining: remove another 6,137 bytes to get strictly below 30,000, then complete final repository verification. No game was launched.

Passive GC string initializers

Release emits GC string literals of at least 32 UTF-8 bytes with array.new_data instead of one constant instruction per byte. One passive segment stores the initializer bytes, deduplicating identical text while preserving separate GC allocations. Bytes are collected only when a constructor is actually emitted; discarded error payloads and unreachable functions retain no initializer data. This directly reduces the diagnostic-string overhead introduced by source-defined Unity discovery. General compiler optimization passes remain deferred.

Explicit Lunistice shrinks 36,136 -> 34,436 bytes (-1,700); automatic selection shrinks 158,930 -> 147,572 bytes (-11,358). The explicit artifact removes 2,553 code bytes and adds 853 bytes of passive data and section metadata. None of the 38 baseline artifacts grows. Source fingerprints, type/function counts, helper sets, scratch/read capacities, and initial pages are unchanged. Passive bytes occupy no linear memory. Debug encoding remains unchanged: the checked IL2CPP fixture is byte-identical excluding compiler-identity metadata.

All 687 compiler tests pass, including demand-driven initializer retention and Unicode allocation across suspensions. The full public runtime catalog validates 170 artifacts and passes 208 scenarios; both Release Lunistice editions pass. Documentation validation (561 pages) and Clippy with warnings denied pass. Evidence: target/unity-passive-strings-*.log.

Remaining: remove another 4,437 bytes to get strictly below 30,000, then complete final repository verification. No game was launched.

Follow-up verification and rejected experiment

The reviewed 38-artifact baseline passes its strict comparison, including both Release Lunistice editions (target/unity-passive-strings-baseline-strict.log). Both profile importers also pass --check against the pinned ASR audit checkout (target/unity-final-mono-profiles.log, target/unity-final-il2cpp-profiles.log).

The seven private adapter tests in src/managed_collection_layout_tests.rs still need a fresh run. Building the full library-test executable exceeded the 1,536 MiB hard process-tree cap with native optimization levels 3, 1, and 0. These are build-resource failures, not executed test failures. The cap remains unchanged. Logs: target/unity-final-private-build*.log. The successful public compiler/runtime results above do not substitute for these private fixtures.

An experiment replacing metadata-name debug arrays with joined strings was reverted: explicit Lunistice grew by 43 bytes and retained the same helpers. Its build and report (target/unity-name-diagnostics-*) are not evidence for the retained source. In particular, target/release binaries and the ephemeral target/unity-baseline/report.json from that experiment need rebuilding before further measurements; the checked-in baseline records the accepted 34,436 bytes.

Inspection identified a specific follow-up: codegen/dependencies.rs retains all structural debug helpers whenever any structural debug formatter is used. Determine the actual formatter requirements before changing that dependency rule. No prospective saving is counted. Default async-frame construction was also investigated but left unchanged because the immutable dispatch tag prevents a direct struct.new_default substitution.

Private adapter verification completed

All seven groups from src/managed_collection_layout_tests.rs now pass against the retained compiler source at 460eb35. To avoid compiling unrelated library unit tests, a temporary Cargo example used a copy of the production crate root plus the existing fixture module, removing only its #[test] attributes and calling the seven functions sequentially. No assertions, input fixtures, Node harnesses, or production behavior were changed. The temporary example entry and source were removed after execution.

The guarded build passed at 1,473 MiB peak and execution at 634 MiB. Both Wasm profiles cover collection type contracts, list layouts, keyed layouts/slots, Mono declaring-owner static paths, and IL2CPP plain/generic storage. The latter two run 462 and 330 cases respectively in each profile. Evidence: target/unity-private-runner-build.log, target/unity-private-runner-runtime.log. This closes the private-fixture gap above without raising the memory cap.

The first field-grouping prototype was removed before compilation: grouping lookups before managed demand pruning would retain unused field lookups. Any future grouping must be selected only after actual field demand is known and preserve the individual-lookup path for partially used schemas. No source or size change from that prototype is retained. The accepted size remains 34,436.

Group required-field binding after demand analysis

Classes with at least four demanded required fields share one async binding loop. The existing managed-demand pass selects and compacts the group's input names/static flags and remaps its result slots before lowering. Smaller groups keep individual lookups. Adding an unused field cannot retain its discovery or disable grouping for the fields that are used. Conditional fields keep their existing completed-absence handling. Declaration order, retries, cancellation, and inherited static storage owners are preserved on both backends and through automatic selection. Lunistice source and diagnostic text are unchanged.

The new loops exposed an existing type leak: range layouts were emitted even when unreachable. Range layouts now follow the same reachability requirement as other GC types. This removes the prototype's native-only size increase; the native fixture shrinks from 615 to 597 bytes. No general optimization pass was introduced.

Explicit Lunistice shrinks 34,436 -> 33,234 bytes (-1,202); automatic selection shrinks 147,572 -> 145,268 bytes (-2,304). Preparation's poll body shrinks from 4,453 to 3,167 bytes. None of the 38 baseline artifacts grows; their source fingerprints, runtime helper sets, scratch/read capacities, linear static data, and initial page counts are unchanged. The final subset-compaction refinement leaves all 38 artifact sizes, sections, function bodies, and emission reports identical to the first version with range pruning.

All 689 compiler tests passed before the final subset-compaction refinement. After refinement, all 53 profile/code-generation tests pass, including scalar unused-field exclusion, grouped reads with an unused nested Map, and unused range exclusion. For the Map case, existing GC type indices can reorder without changing size or retaining additional functions/helpers; requiring identical type numbering would exceed the demand-driven guarantee being tested.

Evidence: target/unity-binding-batch-compiler.log, target/unity-binding-batch-subset-profiles.log, and target/unity-binding-batch-subset-baseline.log.

The final public runtime run validates 172 artifacts and passes all 210 scenarios. The grouped cursor fixture adds 28 cases in each build profile, covering both pointer widths/backends, inherited static owners, ambiguity, bounded traversal, and repair after a later field's metadata fails. An unused nested Map field is deliberately absent from the simulated game. Both Release Lunistice editions pass. Documentation validation (561 pages), Rustfmt, and Clippy with warnings denied pass. Logs: target/unity-binding-batch-subset-runtime.log, target/unity-binding-batch-docs.log, target/unity-binding-batch-clippy.log.

Remaining: remove another 3,235 bytes to get strictly below 30,000, then complete the final size acceptance. No game was launched.

Remove array pretty-printing from lookup diagnostics

Unity's seven class/field lookup diagnostics now join candidate names directly. They retain the failure reason and names without invoking structural array formatting. The shared field cursor's error branch must use the same formatting: changing only the outer lookup messages leaves the array formatter reachable. No lookup, alias expansion, suspension, or Lunistice source behavior changes.

Explicit Lunistice shrinks 33,234 -> 32,353 bytes (-881); automatic selection shrinks 145,268 -> 144,418 bytes (-850). Five functions disappear: the array formatter and QuoteDebugString, IndentDisplay, WrapDebugEntry, and WrapDebugVariant. None of the 38 baseline artifacts grows; their sources, scratch/read capacities, linear static data, and initial page counts are unchanged. Native and non-Unity fixtures are unchanged in size.

All 172 public runtime artifacts validate and all 210 scenarios pass. Field cursor rejection cases now assert that the same message contains both aliases and the failure reason, across both backends and widths, in both build profiles. Both Release Lunistice editions, the reviewed baseline gate, and documentation validation pass. Evidence: target/unity-name-diagnostics-runtime.log, target/unity-name-diagnostics-strict.log, and target/unity-name-diagnostics-docs.log. All builds and tests ran serially under the existing memory limits. No game was launched.

Remaining: remove another 2,354 bytes to get strictly below 30,000, then complete final size acceptance. Directly returning a child future from a synchronous forwarding function is not currently supported; do not turn that into a general compiler optimization project as part of this migration.

Share conditional field binding with required fields

The existing demand-selected binding loop now includes conditional fields. Its result separates final offsets/static addresses from original field metadata, so required fields read their values directly and conditional fields retain completed absence. Required absence still rejects attachment; unreadable metadata still retries. Inherited static storage uses the declaring owner. No partial group becomes visible to state reads. Small demanded groups retain their individual lookup paths, and unused required or conditional fields are removed before grouping and result-slot assignment.

Explicit Lunistice shrinks 32,353 -> 32,121 bytes (-232); automatic selection shrinks 144,418 -> 142,234 bytes (-2,184). The explicit preparation poll shrinks from 3,166 to 2,904 bytes. The other 36 measured artifacts are unchanged in size. All 38 preserve their source fingerprints, runtime helper sets, scratch/read capacities, linear static data, and initial page counts.

All 54 profile/code-generation tests pass, including unused nested Maps inside conditional groups. All 172 public runtime artifacts validate and all 210 scenarios pass. The grouped fixture runs 36 cases per build profile across both backends and widths, including optional absence, required absence, late metadata, inherited owners, and bounded traversal. Both Release Lunistice editions, the reviewed baseline gate, documentation validation (561 pages), and Clippy with warnings denied pass. Evidence: target/unity-optional-batch-slots-profiles.log, target/unity-optional-batch-slots-runtime.log, target/unity-optional-batch-slots-strict.log, target/unity-optional-batch-slots-docs.log, and target/unity-optional-batch-slots-clippy.log.

The full compiler suite also passes all 690 tests against this final source (target/unity-optional-batch-slots-compiler.log); Rustfmt passes. Every command used the existing serialized memory guard, without raising its limits.

Complete: retained at a7fff23 under the user's roughly-500-byte stopping criterion. Explicit output is 32,121 bytes; automatic output is 142,234 bytes. No requested feature remains outstanding. No game was launched.

After discarding the two small experiments, the restored implementation was rebuilt under the unchanged memory cap. The fresh baseline matches all 38 accepted artifact sizes and passes Lunistice base/DLC behavior; documentation validation also passes. Evidence: target/unity-closeout-build.log, target/unity-closeout-baseline.log, and target/unity-closeout-docs.log.