Skip to content

chore: new lods through ISS descriptor#8870

Open
dalkia wants to merge 18 commits into
devfrom
chore/iss-v2
Open

chore: new lods through ISS descriptor#8870
dalkia wants to merge 18 commits into
devfrom
chore/iss-v2

Conversation

@dalkia
Copy link
Copy Markdown
Collaborator

@dalkia dalkia commented May 28, 2026

Pull Request Description

What is "Initial Scene State (ISS)"?

When you walk through Decentraland and a scene comes into view, the runtime needs to fetch, instantiate, and render every asset in that scene before you see anything. For a complex parcel that can take seconds — the scene "pops in" once it's ready. ISS is a streaming optimization that gives the player something to look at while the real scene loads: a lightweight, pre-baked snapshot of the scene's static geometry that the LOD pipeline can show immediately, then seamlessly hand off to the SDK runtime once the real scene is ready.

How it relates to the existing LOD pipeline

Decentraland already has a distance-based LOD system: each scene has multiple progressively-simpler versions baked (LOD_0 and LOD_1). Until ISS, LOD_0 (the closest, highest-quality version) was a separate copy that the client downloaded in full, instantiated as its own GameObjects, and then threw away and re-downloaded the same assets again once the player walked close enough to trigger the real SDK scene — so we paid the bandwidth and instantiation cost twice for the same geometry.

ISS replaces LOD_0 with a smarter representation: instead of duplicating the scene's assets in a separate LOD bundle, the descriptor points at the same per-asset bundles the SDK runtime will load anyway, plus the transforms needed to show them at preview time. The LOD path and the SDK runtime now share the underlying GameObjects via a "bridge" — when the LOD instance is no longer needed (the player walked close enough), it transitions into the running scene instead of being destroyed and re-created. No double download, no double instantiation.

LOD_1+ are unchanged and still used as-is for far-distance fallback. They're produced by the new LOD generator: https://github.com/decentraland/lod-generator-unity

How the descriptor reaches the client

For each scene the bake pipeline can publish a small JSON descriptor next to the asset bundles. The descriptor lists which GLTF assets to show as the "initial state" and where to place them (position / rotation / scale). The client fetches the descriptor as soon as the scene starts coming into range, instantiates the listed assets at LOD distance, and reuses them via the bridge described above when the SDK scene starts.

The two paths an ISS descriptor can take:

  • Descriptor mode (this PR): the descriptor is a standalone JSON file at the LOD manifest bucket; the client fetches each listed asset as its own asset bundle from the standard AB CDN.
  • Bundle mode (legacy, deferred to a follow-up): the descriptor + every listed asset are packed into one shared staticscene_<sceneId><platform> AB.

What does this PR change?

The old ISS shipped the descriptor inside the single bundled AB (the staticscene_* one). To read the descriptor, the client had to download and open that whole AB. With ISS evolving toward shared GameObjects between LOD and SDK runtime, two things changed:

  1. The descriptor is now a standalone JSON file at the LOD manifest bucket — small, easy to fetch, easy to disk-cache, no AB unpack required.
  2. The Bundle-mode AB is not currently being built. There are asset bundles reusability concerns to be solved in the asset bundle converter before we can publish the shared staticscene_* bundles correctly. So for now the per-asset bundles produced by the converter for the regular scene load are what gets served; the shared bundle is deferred to a follow-up PR + bake-side coordination.

Most of the diff in this PR is the consequence of those two shifts: the descriptor moves from embedded-in-AB to a freestanding JSON loader with its own disk cache, an ECS component on each scene entity that the radius gate consults lazily, and a digest-aware shared GLTF cache so the LOD path and the SDK runtime hit the same underlying assets without colliding in Unity's AB tracker.

The Bundle-mode plumbing is structurally kept (state enum, dormant queries) so reviving it later is a small targeted change.

Highlights

  • ISSDescriptor is an ECS component on each scene entity (class with mutable internal state — same reference for the scene's lifetime). Constructed in Uninitialized for scenes that may have ISS, or None for opt-outs (PX / static-pointer / smart-wearable previews). The resolver mutates state in place via MarkResolved, so cached references in OrderedDataManaged and GetSceneFacadeIntention see the resolved state without a re-fetch — no World.Get in the gate hot path.
  • Radius-gated resolution. ResolveSceneStateByIncreasingRadiusSystem attaches the resolver promise on the first SHOWING_LOD / SHOWING_SCENE transition for an Uninitialized scene, then bails for one tick until ResolveISSDescriptorSystem consumes the promise. Scenes that never come into range never trigger a descriptor lookup.
  • ResolveISSLODSystem (descriptor mode) spawns one AB promise per descriptor entry, applies the transform, counts failures via AddFailedAsset so AllAssetsInstantiated settles even when individual assets 404. Bridge handoff to the SDK runtime is reservation-based (ISSDescriptor.TryReserveBridgeSlot) capped at the descriptor's per-hash multiplicity, so SDK-instantiated copies of the same hash at runtime never pollute the bridge.
  • Digest-aware GLTF cache reuse. Both LOD and SDK runtime paths key the GLTF container cache by hash@digest via AssetBundleManifestVersionExtensions.ComposeCacheKey, so bridged assets round-trip cleanly between the two without spawning duplicate instances. Per-asset AB promises also populate GetAssetBundleIntention.DepsDigest from the manifest, making the (Hash, DepsDigest) cache key match the SDK runtime's — this eliminated the "asset bundle already loaded" Unity error from parallel loads of the same physical bundle.
  • SupportsISS() on AssetBundleManifestVersion mirrors SupportsDepsDigests(). Cached version-number check; gates the descriptor lookup so pre-v49 manifests skip the network round-trip.
  • Disk-cached descriptors with text-editor-readable JSON. None results are not persisted (decorator skips).

Files of interest

  • Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptor.cs — descriptor + bridge state + JSON DTOs + MarkResolved (in-place mutation)
  • Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.csLoadSystemBase<ISSDescriptor, GetISSDescriptor> hitting the LOD bucket
  • Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/IncreasingRadius/ResolveSceneStateByIncreasingRadiusSystem.cs — the gate that spawns the resolver promise on first LOD/Scene transition
  • Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/ResolveISSDescriptorSystem.cs — consumes the promise, mutates the entity's descriptor in place
  • Explorer/Assets/DCL/LOD/Systems/ResolveISSLODSystem.cs — descriptor mode + digest-aware cache reuse + bridge slot reservation

Removed: EarlySceneRequestSystem + EarlyAssetBundleRequestSystem

Removed until we decide how to use them effectively just with the descriptor. For cognitive and scope reasons, this will be done on a later PR.

Deferred to a follow-up PR: Bundle mode

The legacy single-bundle ISS path (one shared staticscene_<sceneID><platform> AB containing every listed asset) is structurally kept but disabled at the loader level. LoadISSDescriptorSystem.IsBundleReachableAsync is commented out and every ISS-capable scene resolves directly to IISSDescriptor.State.Descriptor for now. ISSDescriptor.SupportsBundle() returns false so the ResolveISSLODSystem bundle branch is dormant. The plumbing (Bundle state enum value, bundle-mode query, helper functions) is left in place so re-enabling is a one-line restore in a later PR.

Doing this in two steps keeps the cognitive load of the current review focused on the descriptor pipeline; bundle mode will come back with its own targeted PR + bake-side coordination.

Important: AB Converter doesn't bake ISS bundles yet

The asset bundle converter doesn't currently have ISS bundle baking enabled. This PR can be exercised end-to-end against v49+ manifests for the descriptor-only path — but if you point it at a realm whose scenes were not baked with descriptor JSONs in the LOD bucket, you'll just see scenes resolve to None (no ISS) and fall through to the legacy LOD path. The descriptor-mode happy path requires both the descriptor JSON and per-asset bundles to exist at the expected URLs.

Test Instructions

Steps (standard run):

metaforge explorer run XXXX  # ← replace with this PR number

Test Steps

  1. Genesis Plaza (zone) — ISS happy path.
    Spawn into Genesis Plaza on zone and explore: walk around, move outward and back, change directions.

    • LOD preview pops in early via ISS (less noticeable blank moments before geometry shows).
    • When you walk close enough to trigger the real scene, the LOD geometry transitions into the SDK scene without duplicate copies of the same asset, and without "asset bundle already loaded" errors in the log.
    • Scenes outside your loading radius unload cleanly and re-load on the way back.
    • Spawn on a remote coordinate (-15,-20 ie) and walk towards Genesis Plaza. See that the scene loads correctly
    • Go to 100,100. Press the Memory Full Button in the debug menu. The memory should go down considerably.
      When it stabilizes, go back to 0,0. Repeat the previous steps, all should work
  2. Regression — every other scene-loading path. Sanity-check that nothing else broke:

    • Genesis City on org — descriptors don't exist there yet, so every scene should fall through to the legacy LOD path (one 403 per scene from the LOD bucket is expected and now suppressed in the log; behavior should otherwise be identical to main).
    • LSD scene (Local Scene Development) — load a scene locally; should never touch the descriptor pipeline (static-pointer scenes opt out at construction with State.None).
    • Worlds (fixed-realm) — connect to a Worlds realm. Fixed-pointer scenes go through the descriptor gate too, so missing descriptors should yield the same fall-through behavior as Genesis on org. The world should still load.
    • Portable experiences — equip / spawn a PX. Smart wearables count here too. Both opt out of ISS at construction; they should load exactly as on main.

Quality Checklist

  • Changes have been tested locally
  • Documentation has been updated (if required)
  • Performance impact has been considered
  • For SDK features: Test scene is included

Code Review Reference

Please review our Branch & PR Standards before submitting.

🤖 Generated with Claude Code

dalkia and others added 4 commits May 27, 2026 23:56
…lution

Reworks the **Initial Scene State (ISS)** loading path so a scene's preview
can be served by a JSON descriptor (`<sceneId>_InitialSceneState.json` on the
LOD manifest bucket) listing per-asset hashes + transforms, with each asset
fetched as its own AB. The legacy single-AB bundle path is kept structurally
but currently disabled at the loader level — to be revived in a follow-up.

### Highlights

- **`ISSDescriptor` is an ECS component on each scene entity** (class semantics).
  Constructed in `Uninitialized` state for scenes that may have ISS, or `None`
  for opt-outs (PX, static-pointer/LSD, smart-wearable previews). The resolver
  mutates state in place via `MarkResolved` so cached references in
  `OrderedDataManaged` and `GetSceneFacadeIntention` see the resolved state
  without a refetch.
- **Radius-gated resolution.** `ResolveSceneStateByIncreasingRadiusSystem`
  spawns the resolver promise on the first SHOWING_LOD / SHOWING_SCENE
  transition for a scene with `Uninitialized` descriptor, then bails for one
  tick until `ResolveISSDescriptorSystem` consumes the promise. No descriptor
  resolution work for scenes that never come into range.
- **Descriptor mode (new): `ResolveISSLODSystem`** spawns one AB promise per
  descriptor entry, applies the transform, counts failures so
  `AllAssetsInstantiated` settles even on 404s. Bridge handoff to the SDK
  runtime is reservation-based (`ISSDescriptor.TryReserveBridgeSlot`) capped
  at the descriptor's per-hash multiplicity, so SDK-instantiated copies of
  the same hash created at runtime never pollute the bridge.
- **Digest-aware GLTF cache reuse.** Both LOD and SDK runtime paths key the
  GLTF container cache by `hash@digest` via
  `AssetBundleManifestVersionExtensions.ComposeCacheKey`, so bridged assets
  round-trip cleanly between the two without spawning duplicate instances.
  Per-asset AB promises populate `GetAssetBundleIntention.DepsDigest` from
  the manifest, making the `(Hash, DepsDigest)` cache key match the SDK
  runtime's — eliminates the "asset bundle already loaded" Unity error from
  parallel loads of the same physical bundle.
- **`SupportsISS()` on `AssetBundleManifestVersion`** — mirrors
  `SupportsDepsDigests()`. Cached version check; gates the descriptor lookup
  to v49+ manifests.
- **Disk-cached descriptors** with text-editor-readable JSON format. NONE
  results are not persisted to disk (decorator skip).

### Files of interest

- `Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptor.cs`
  — descriptor + bridge state + JSON DTOs + in-place mutation
- `Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs`
  — `LoadSystemBase<ISSDescriptor, GetISSDescriptor>` hitting the LOD bucket
- `Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/IncreasingRadius/ResolveSceneStateByIncreasingRadiusSystem.cs`
  — the gate that spawns the resolver promise on first LOD/Scene transition
- `Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/ResolveISSDescriptorSystem.cs`
  — consumes the promise, mutates the entity's descriptor in place
- `Explorer/Assets/DCL/LOD/Systems/ResolveISSLODSystem.cs` — descriptor mode
  + digest-aware cache reuse + bridge slot reservation

### Removed

- `EarlySceneRequestSystem` + `EarlyAssetBundleRequestSystem` and their flag
  types. The prewarm chain only made sense when the ISS descriptor was
  resolved synchronously during scene-definition loading; with lazy
  resolution there's no longer a deterministic point to usefully prefetch
  the bundle. A follow-up PR will revive the prewarm in a form that fits
  the lazy pipeline.

### Temporarily forced

- Descriptor mode is the only path enabled in `LoadISSDescriptorSystem`
  while we validate the per-asset descriptor pipeline end-to-end.
  `IsBundleReachableAsync` and the Bundle-mode plumbing are kept commented
  for a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The squash from chore/new-iss to chore/iss-v2 lost this single line. Unity's
runtime AB tracker keeps the AssetBundle registered until UnloadAB is called,
so a second fetch of the same URL (LOD path → SDK runtime, or any other
cross-path reuse) hits "AssetBundle ... can't be loaded because another
AssetBundle with the same files is already loaded." Calling UnloadAB right
after the Object[] is extracted lets the file handle go but keeps the loaded
assets — matching the pre-merge behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

badge

New build in progress, come back later!

dalkia and others added 2 commits May 28, 2026 01:15
…03 spam

- CreateSceneEntity now takes the ISSDescriptor explicitly; each caller picks
  the right starting state for its scene type.
- LoadFixedPointersSystem passes Uninitialized — Worlds (fixed-realm) scenes
  go through the same AB pipeline as Genesis and may have ISS descriptors.
- LoadStaticPointersSystem / portable-experience / smart-wearable paths pass
  None — those aren't deployed through the regular AB pipeline.
- MockSceneData.ISSDescriptor returns the NONE singleton instead of null.
- LoadISSDescriptorSystem.TryLoadDescriptorAsync re-enables suppressErrors
  on the GetAsync — descriptor-missing is the expected case for non-ISS scenes
  and was spamming the [SCENE_LOADING] log every realm load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dalkia dalkia self-assigned this May 28, 2026
@dalkia dalkia marked this pull request as ready for review May 28, 2026 04:24
@dalkia dalkia requested review from a team as code owners May 28, 2026 04:24
@github-actions github-actions Bot requested a review from anicalbano May 28, 2026 04:24
@claude

This comment has been minimized.

@dalkia dalkia changed the title chore: ISS via standalone descriptor (descriptor-only, bundle deferred) chore: iss via standalone descriptor (descriptor-only, bundle deferred) May 28, 2026
Introduces ISSDescriptorResolution (readonly struct: state + assets) as
the TAsset of LoadISSDescriptorSystem and the disk-cache value type. The
per-entity ISSDescriptor class now owns only the runtime concerns
(bridge slots, MarkResolved) and takes a resolution to apply. The loader
no longer hands out a half-initialized class instance that exists solely
to be unpacked by the resolver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@m3taphysics
Copy link
Copy Markdown
Collaborator

PR #8870, run #26555773261

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2700 2700
CPU average 33.3 ms 33.3 ms 0.0 ms -0.0%
CPU 1% worst 34.4 ms 33.8 ms 0.7 ms -2.0%
CPU 0.1% worst 41.1 ms 35.4 ms 5.7 ms -16.0% 🔴
GPU average 7.4 ms 8.1 ms -0.6 ms 7.9% 🟢
GPU 1% worst 20.0 ms 17.2 ms 2.8 ms -16.5% 🔴
GPU 0.1% worst 23.3 ms 20.5 ms 2.8 ms -13.5% 🔴

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@m3taphysics
Copy link
Copy Markdown
Collaborator

PR #8870, run #26557791876

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2701 2700
CPU average 33.3 ms 33.3 ms -0.0 ms 0.0%
CPU 1% worst 33.5 ms 33.6 ms -0.1 ms 0.3%
CPU 0.1% worst 35.2 ms 36.0 ms -0.8 ms 2.3%
GPU average 8.5 ms 8.7 ms -0.3 ms 2.9%
GPU 1% worst 17.2 ms 18.0 ms -0.8 ms 4.5% 🟢
GPU 0.1% worst 25.2 ms 24.2 ms 1.0 ms -4.3% 🔴

Copy link
Copy Markdown
Contributor

@NickKhalow NickKhalow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid work! Please address the comments below

Comment thread Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/IISSDescriptor.cs Outdated
for (var i = 0; i < source.Count; i++) assets.Add(source[i]);

string json = JsonUtility.ToJson(new ISSDescriptorMetadata { assets = assets });
byte[] payload = Encoding.UTF8.GetBytes(json);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need UTF8 representation? The data can be stored as UTF16 without allocating additional buffers. MemoryIterator can read directly on str bytes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stepping further, we could store the blob without serializing it into json. Please add a note for the potential optimization

Copy link
Copy Markdown
Collaborator Author

@dalkia dalkia May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how would I address this one. Are you reffering on doing a simple Encoding.Unicode.GetBytes(json) and Encoding.Unicode.GetString(data.Memory.Span), respectively?

Copy link
Copy Markdown
Contributor

@NickKhalow NickKhalow May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, I'm referring to pinning the string and reading it via char*

unsafe
{
    fixed (char* p = json)
    {
        byte* bytes = (byte*)p;
        int byteCount = json.Length * sizeof(char);
    }
}

Comment thread Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFacade.cs Outdated
Comment thread Explorer/Assets/DCL/Infrastructure/Global/Dynamic/GlobalWorldFactory.cs Outdated
@decentraland decentraland deleted a comment from claude Bot May 29, 2026
@decentraland decentraland deleted a comment from claude Bot May 29, 2026
@decentraland decentraland deleted a comment from claude Bot May 29, 2026
@decentraland decentraland deleted a comment from claude Bot May 29, 2026
@decentraland decentraland deleted a comment from claude Bot May 29, 2026
@decentraland decentraland deleted a comment from claude Bot May 29, 2026
@decentraland decentraland deleted a comment from claude Bot May 29, 2026
@claude

This comment has been minimized.

Comment thread Explorer/Assets/DCL/LOD/Systems/ResolveISSLODSystem.cs
Comment thread Explorer/Assets/DCL/LOD/Systems/ResolveISSLODSystem.cs Outdated
@m3taphysics
Copy link
Copy Markdown
Collaborator

PR #8870, run #26619640424

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2700 2699
CPU average 33.3 ms 33.3 ms 0.0 ms -0.0%
CPU 1% worst 33.9 ms 33.9 ms -0.0 ms 0.0%
CPU 0.1% worst 37.3 ms 36.8 ms 0.5 ms -1.3%
GPU average 7.4 ms 7.3 ms 0.1 ms -1.5%
GPU 1% worst 19.3 ms 19.9 ms -0.6 ms 3.1% 🟢
GPU 0.1% worst 22.9 ms 23.0 ms -0.1 ms 0.6%

@m3taphysics
Copy link
Copy Markdown
Collaborator

PR #8870, run #26620771499

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2701 2700
CPU average 33.3 ms 33.3 ms -0.0 ms 0.0%
CPU 1% worst 33.5 ms 33.7 ms -0.2 ms 0.5%
CPU 0.1% worst 35.2 ms 37.1 ms -1.8 ms 4.9% 🟢
GPU average 8.3 ms 8.3 ms -0.0 ms 0.3%
GPU 1% worst 18.1 ms 17.9 ms 0.2 ms -1.2%
GPU 0.1% worst 25.0 ms 24.8 ms 0.2 ms -0.9%

@claude

This comment has been minimized.

Comment thread Explorer/Assets/DCL/LOD/Components/InitialSceneStateLOD.cs
@claude

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

🔍 Claude reviewed this PR and found no blocking issues, but assessed it as complex — human DEV review is still required before merging.

@m3taphysics
Copy link
Copy Markdown
Collaborator

PR #8870, run #26657522104

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2701 2699
CPU average 33.3 ms 33.3 ms -0.0 ms 0.0%
CPU 1% worst 33.6 ms 34.2 ms -0.7 ms 1.9%
CPU 0.1% worst 35.5 ms 41.3 ms -5.9 ms 14.2% 🟢
GPU average 8.2 ms 7.1 ms 1.1 ms -16.2% 🔴
GPU 1% worst 18.7 ms 20.5 ms -1.8 ms 8.7% 🟢
GPU 0.1% worst 26.7 ms 24.6 ms 2.1 ms -8.5% 🔴

@claude

This comment has been minimized.

@m3taphysics
Copy link
Copy Markdown
Collaborator

PR #8870, run #26665063028

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2701 2700
CPU average 33.3 ms 33.3 ms -0.0 ms 0.0%
CPU 1% worst 33.6 ms 33.9 ms -0.3 ms 0.9%
CPU 0.1% worst 35.0 ms 37.3 ms -2.3 ms 6.2% 🟢
GPU average 8.2 ms 8.2 ms -0.0 ms 0.4%
GPU 1% worst 18.4 ms 17.6 ms 0.8 ms -4.3% 🔴
GPU 0.1% worst 27.1 ms 23.3 ms 3.8 ms -16.3% 🔴

@claude

This comment has been minimized.

@m3taphysics
Copy link
Copy Markdown
Collaborator

PR #8870, run #26733266952

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2701 2700
CPU average 33.3 ms 33.3 ms 0.0 ms -0.0%
CPU 1% worst 33.6 ms 33.6 ms 0.0 ms -0.1%
CPU 0.1% worst 35.5 ms 33.8 ms 1.7 ms -5.1% 🔴
GPU average 8.6 ms 7.8 ms 0.7 ms -9.6% 🔴
GPU 1% worst 17.8 ms 21.8 ms -4.0 ms 18.4% 🟢
GPU 0.1% worst 22.6 ms 25.7 ms -3.1 ms 11.9% 🟢

@dalkia dalkia changed the title chore: iss via standalone descriptor (descriptor-only, bundle deferred) chore: new lods through ISS descriptor Jun 1, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jun 1, 2026

Claude finished @dalkia's task in 4m 10s —— View job


Tasks

  • Gather context (CLAUDE.md, docs/README.md)
  • Check new commits since last review
  • Read changed files
  • Analyze diff for blocking issues
  • Post review findings

Code Review

What this PR fixes: Migrates the ISS descriptor from being embedded inside a shared staticscene_* asset bundle to a standalone JSON file fetched from the LOD manifest bucket. The descriptor is now an ECS component on each scene entity with radius-gated resolution, a digest-aware GLTF cache, and bridge-slot reservation for LOD↔SDK handoff.


New Commit Review (44efcd42 — "StringDiskDeserializer using UTF16")

This commit addresses NickKhalow's review comment to avoid the UTF-8 intermediate buffer when serializing strings to disk, by moving StringDiskSerializer from SceneRuntime.Factory.JsSource into ECS.StreamableLoading.Cache.Disk (IDiskCache.cs) and having ISSDescriptorDiskSerializer delegate to it.

Changes summary:

  • StringDiskSerializer relocated from SceneRuntime/Factory/JsSource/StringDiskSerializer.cs to IDiskCache.cs (ECS.StreamableLoading.Cache.Disk)
  • ISSDescriptorDiskSerializer now delegates serialize/deserialize to StringDiskSerializer (UTF-16 path, no Encoding.UTF8 allocation)
  • Removed unused MemoryPool<byte> POOL field from the old StringDiskSerializer
  • StaticContainer.ISSDescriptorDiskCache type updated to reference the new StringDiskSerializer.State

Compilation safety: Both Bootstraper.cs and JsCodeDiskSerializerShould.cs already had using ECS.StreamableLoading.Cache.Disk; before this commit, so StringDiskSerializer resolves from the new namespace without changes to those files. Their using SceneRuntime.Factory.JsSource; remains valid for other types (CachedWebJsSources, etc.).

Non-blocking observations

  1. Stale using System.Text; in ISSDescriptorDiskSerializer.cs:3Encoding.UTF8 is gone; this import is now unused. Won't block compilation but adds noise.

  2. Stale using SceneRuntime.Factory.JsSource; in JsCodeDiskSerializerShould.cs:6StringDiskSerializer has moved out of that namespace. If no other type from that namespace is used in the test file, this import is dead. Harmless but can be cleaned up.

  3. async UniTask over a synchronous call in ISSDescriptorDiskSerializer.DeserializeAsync (line 28) — sds.DeserializeAsync returns UniTask.FromResult(output) (no actual async work). Wrapping it in async UniTask generates an unnecessary state machine. Minimal impact for a disk-cache deserialization path, but aligning with CLAUDE.md §9 ("Minimize detached UniTask/UniTaskVoid calls") would mean returning UniTask.FromResult(JsonUtility.FromJson<ISSDescriptorMetadata>(sds.DeserializeSync(data))) or a synchronous-only path. Not a blocker.

No blocking issues in this commit.


Full Issue Tracker

Issue Source Status
Per-asset helper entities leak on world teardown Claude review ✅ Fixed — AbortISSHelperEntities in UnloadSceneLODSystem.FinalizeComponents
Spurious bridge-release warning on first LOD load Claude review ✅ Fixed — TryReleaseBridgeSlot silent no-op when count ≤ 0
Null-forgiving ! on potentially-null manifest Claude review ✅ Fixed — ?? AssetBundleManifestVersion.CreateFailed()
_NoISS suffix on all assets Claude review ✅ Fixed — neutral AB:{name}_{version}_{source} format
ParentContainer alive on PROCESSING cancellation Claude review ✅ Fixed — UnityObjectUtils.SafeDestroy(ParentContainer) in ForgetLoading
IISSDescriptor interface (single impl, no tests) NickKhalow ✅ Removed
GetISSDescriptor.cs not Intention-suffixed NickKhalow ✅ Renamed
SceneId nullable annotation inconsistency NickKhalow ✅ Non-nullable enforced
hashCapacity not readonly NickKhalow private readonly
Log for failed descriptor resolution NickKhalow ReportHub.Log in ResolveISSDescriptorSystem.cs:47
ISSDescriptorResolution union-type concern NickKhalow ✅ Class removed entirely
Intermediate task in LoadSceneSystemLogicBase NickKhalow ✅ Direct await
StaticContainer concrete type NickKhalow DiskCache<ISSDescriptorMetadata, ...>
ISSDescriptorDiskCache decorator silent-drop NickKhalow ✅ Class removed entirely
Disk serializer optimization note NickKhalow //TODO (opti): comment present
TODO markers in LoadISSDescriptorSystem NickKhalow ✅ Both present
iss.json file extension NickKhalow ✅ Present in GlobalWorldFactory.cs:172
UTF16 serialization (no UTF8 buffer) NickKhalow ✅ Fixed in this commit
Hardcoded dev URL Claude review ⚠️ Acknowledged — author explicitly deferred to follow-up PR

No New Blocking Issues

The new commit cleanly addresses the UTF-16 serializer feedback with no regressions. All previously identified blocking issues remain resolved.

REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches ECS scene lifecycle, LOD pipeline, async AB loading, GLTF container caching, and cross-system component state across multiple subsystems.
QA_REQUIRED: YES

@m3taphysics
Copy link
Copy Markdown
Collaborator

PR #8870, run #26754277020

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2701 2700
CPU average 33.3 ms 33.3 ms 0.0 ms -0.0%
CPU 1% worst 34.0 ms 33.8 ms 0.3 ms -0.7%
CPU 0.1% worst 38.3 ms 36.0 ms 2.3 ms -6.4% 🔴
GPU average 7.2 ms 7.0 ms 0.2 ms -2.9%
GPU 1% worst 19.7 ms 19.6 ms 0.1 ms -0.3%
GPU 0.1% worst 23.5 ms 22.2 ms 1.3 ms -5.9% 🔴

@aixaCode aixaCode added perf_test Run Performance Tests from the PR and removed perf_test Run Performance Tests from the PR labels Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants