Skip to content

chore: abgen-lods flip for LOD bundles and ISS descriptors - #10114

Merged
dalkia merged 7 commits into
devfrom
chore/abgen-lod
Sep 21, 2026
Merged

dalkia merged 7 commits into
devfrom
chore/abgen-lod

Conversation

@dalkia

@dalkia dalkia commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Pull Request Description

Temporary by design. Everything here is scaffolding for validating abgen's LOD generation against production. Once we confirm the abgen LODs are right, abgen becomes the only LOD source and all of it goes away: the alfa-abgen-lods flag, the --abgen-lods arg, ResolveAbgenLodsUrl and its sub-path juggling, and the AbgenLodsCacheKey namespacing that only exists so two sources can coexist in one cache. Review it as a switch we intend to delete, not as an abstraction to build on.

What does this PR change?

TL;DR — LOD bundles and ISS descriptors can now come from abgen instead of ab-cdn / lod-generator-unity-cdn, flipped together by the alfa-abgen-lods flag (or --abgen-lods). The flag is already on in org and zone, so merging puts clients on abgen LODs right away.

  • Both LOD urls flip together through DecentralandUrlsSource.ResolveAbgenLodsUrl — the descriptors and the bundles describe one generation, like the abgen registry and abgen-cdn do for asset bundles. Asset bundles and the registry do not follow the LOD flip.
  • abgen keeps its LOD generation under one LOD/ prefix, beside the asset bundles on the same CDN: /LOD/{level}/{sceneId}_{level}_{platform} for the bundles (that segment comes from the LOD manifest version) and /LOD/lods-unity/manifests/{sceneId}_InitialSceneState.json for the descriptors, whose path carries none — so only the descriptor base takes the prefix. The regular hosts keep their layout.
  • Descriptor names carry a lower-cased scene id and the bucket is case-sensitive, so LoadISSDescriptorSystem lower-cases it as the LOD bundle path already does. Mixed-case Qm… ids were 404ing verbatim.
  • Each LOD source gets its own cache namespace — LOD files are named by scene id only. IDecentralandUrlsSource.AbgenLodsCacheKey (null on the regular pipeline) replaces the literal "dummyDate" build date in UpdateSceneLODInfoSystem and salts the ISS descriptor disk-cache key. With the flip off both keys are byte-identical to today, so no existing cache is invalidated.
  • ISS assets need nothing — they resolve through the scene's own manifest, so they follow the abgen-pipeline flip. abgen's manifest is always v1002+, so its scenes always clear the v49 ISS gate; abgen publishes no LOD/0 because level 0 is the ISS path. Hence the two flips belong on together.

Routes verified live against abgen-cdn.decentraland.org on a sample of Genesis scenes (bafkrei… and Qm… ids). Tests in DecentralandUrlsSourceShould: flag on (asserting the /LOD descriptor base and the cache key), the launch arg, and off.

Test Instructions

Smoke test — run around Genesis City and check LODs look normal:

metaforge explorer run 10114

alfa-abgen-lods is already enabled in org, so no launch arguments are needed — LODs should simply be visible, loading in at distance the way they do today. Nothing should look missing, untextured, or mispositioned versus a dev build.

Expected result: walking out from Genesis Plaza across the city, distant scenes fill in with their LOD meshes as usual, and close scenes resolve to the real thing. No holes where a LOD should be.

Then a world — worlds have their own LOD state, so check one too:

/goto swissverse.dcl.eth

First set the scene load distance to 20 in Settings (SceneLoadRadius), otherwise you never get far enough out for the LOD to kick in. Swissverse has one massive scene: walk away from it and it should swap to its LOD_1 — a visibly simplified but correctly placed and textured version of the same silhouette, not a hole and not a mispositioned block.

Confirm with Player.log

  • macOS: ~/Library/Logs/Decentraland/Explorer/Player.log
  • Windows: %USERPROFILE%\AppData\LocalLow\Decentraland\Explorer\Player.log
  1. The ENABLED FEATURE FLAGS block lists alfa-abgen-lods and alfa-abgen-pipeline — that is what puts the session on abgen.
  2. No LOD AB request for … failed warnings while walking around. A handful means those scenes are missing from the abgen run; a flood means the source is wrong.

Note: the ENVIRONMENT block near the top of the log is printed before feature flags load, so it shows ab-cdn / lod-generator-unity-cdn. That is expected and is not evidence the flip is off.

Quality Checklist

  • Changes have been tested locally
  • Documentation has been updated (if required) — flags and args are documented in code comments; no user-facing docs affected
  • Performance impact has been considered — none at runtime: two extra string compares per LOD request
  • For SDK features: Test scene is included — not applicable

Code Review Reference

Please review our Branch & PR Standards before submitting.

🤖 Generated with Claude Code

Mirror the abgen-pipeline flip for LODs: the `alfa-abgen-lods` feature
flag or the `--abgen-lods` launch arg moves LodAssetBundlesCDN and
LodGeneratorCDN together onto abgen-cdn, since the ISS descriptors and
the LOD bundles describe one generation. The flag's `lods-base-url` text
variant, or `--abgen-lods-base-url`, points both at an arbitrary base
instead (a bucket prefix holding one run), with the production layout
underneath: LOD/{level}/{sceneId}_{level}_{platform} and
lods-unity/manifests/{sceneId}_InitialSceneState.json.

LOD files are named by scene id only, so each LOD source needs its own
cache namespace. IDecentralandUrlsSource.AbgenLodsCacheKey (null on the
regular pipeline) replaces the "dummyDate" LOD manifest build date in
UpdateSceneLODInfoSystem and salts the ISS descriptor disk-cache key in
GetISSDescriptorIntention.DiskHashCompute. Both are read at use time so
a flag that loads after construction is still honoured. Asset bundles and
the registry do not follow the LOD flip.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@dalkia
dalkia requested review from a team as code owners September 15, 2026 20:03
dalkia and others added 3 commits September 16, 2026 07:45
LOD bundles and ISS descriptors now resolve to the published abgen world
LOD_1 run b191e06 (2026-09-15, decentraland/abgen PR #118) unless
--abgen-lods-base-url points elsewhere. The override is always set, so the
--abgen-lods arg and the alfa-abgen-lods flag branches are no longer reached.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
abgen now publishes the LOD generation to abgen-cdn, so the b191e06
prototype bucket default is gone and the "--abgen-lods" arg and the
alfa-abgen-lods flag reach their branches again.

The abgen layout keeps both routes under one "LOD/" prefix:
LOD/{level}/{sceneId}_{level}_{platform} for the bundles, whose segment
already comes from the LOD manifest version, and
LOD/lods-unity/manifests/{sceneId}_InitialSceneState.json for the
descriptors, whose path carries none. ResolveAbgenLodsUrl now appends a
sub-path to every abgen base - empty for the bundles, "/LOD" for the
descriptors - and nothing to the regular hosts, which lay their LOD
generation out as before.

Descriptor file names carry a lower-cased scene id and the bucket is
case-sensitive, so LoadISSDescriptorSystem lower-cases it the way the LOD
bundle path already does; mixed-case Qm ids were 404ing verbatim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Juan Ignacio Molteni <juanignaciomolteni@gmail.com>
@github-actions

github-actions Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Windows and Mac built successfully in Unity Cloud.

Name Links & timing
Build 9984e5d · Logs · built 2026-09-21T13:26:42Z
Windows GitHub job · Unity Cloud #4 · Unity log · ⏱ 27m 25s build + 5m 2s queue · Download .zip · .zip via S3
Mac GitHub job · Unity Cloud #4 · Unity log · ⏱ 25m 25s build + 3m 0s queue · Download .zip · .zip via S3

Lint

Warnings not reduced: 11881 => 11881 — remove at least 1 warning to merge.

Warnings/errors in files changed by this PR (15)
Assets/DCL/LOD/Systems/UpdateSceneLODInfoSystem.cs:99  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs:38  CSharpWarnings::CS8604  Possible null reference argument for parameter 'sceneId' in 'ECS.StreamableLoading.AssetBundles.InitialSceneState.GetISSDescriptorIntention.GetISSDescriptorIntention'
Assets/DCL/LOD/Systems/LODPlugin.cs:44  CSharpWarnings::CS8618  Non-nullable fields 'lodMaterialPool', 'lodTextureArrayContainer', 'cacheCleaner' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the fields as nullable.
Assets/DCL/LOD/Systems/LODContainer.cs:51  CSharpWarnings::CS8618  Non-nullable members 'roadAssetsPrefabList', 'roadsPresence', 'RoadCoordinates' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the members as nullable.
Assets/DCL/LOD/Systems/LODContainer.cs:142  CSharpWarnings::CS8618  Non-nullable property 'LODSettingAsset' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/LOD/Systems/LODContainer.cs:139  CSharpWarnings::CS8618  Non-nullable property 'RoadData' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/LOD/Systems/LODPlugin.cs:98  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/NetworkDefinitions/Browser/DecentralandUrlsSource.cs:294  InconsistentNaming  Name 'AbgenLodsActive' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'abgenLodsActive'.
Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs:19  InconsistentNaming  Name 'GetISSDescriptorIntention' does not match rule 'members_should_be_pascal_case'. Suggested name is 'GetIssDescriptorIntention'.
Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs:28  InconsistentNaming  Name 'LoadISSDescriptorSystem' does not match rule 'members_should_be_pascal_case'. Suggested name is 'LoadIssDescriptorSystem'.
Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs:43  NullnessAnnotationConflictWithJetBrainsAnnotations  Nullability of a member conflicts with annotations in hierarchy
Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs:13  RedundantUsingDirective  Using directive is not required by the code and can be safely removed
Assets/DCL/Infrastructure/Global/Dynamic/GlobalWorldFactory.cs:42  RedundantUsingDirective  Using directive is not required by the code and can be safely removed
Assets/DCL/LOD/Systems/UpdateSceneLODInfoSystem.cs:10  RedundantUsingDirective  Using directive is not required by the code and can be safely removed
Assets/DCL/LOD/Systems/UpdateSceneLODInfoSystem.cs:19  RedundantUsingDirective  Using directive is not required by the code and can be safely removed

Lint run · took 20m 15s

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped Tests time Job time
EditMode ✅ Passed 25987 0 13 3m 48s 13m 59s
PlayMode ✅ Passed 256 0 37 37s 10m 54s

Tests time sums the test cases; Job time is the job's wall clock including checkout, licensing and asset import.

Slowest tests
  • [editmode] 16.5s DCL.AuthenticationScreenFlow.Tests.ProfileFetchingAuthStateShould.CancelStalledFetchOnTimeout
  • [editmode] 10.3s DCL.Tests.Editor.ValidationTests.CheckForDebugUsage
  • [editmode] 10.3s DCL.Tests.Editor.ValidationTests.CheckUnityObjectsForMissingReferences
  • [editmode] 10.0s DCL.Notifications.Tests.NotificationsRequestControllerShould.ReuseSingleListInstanceAcrossPollIterations
  • [editmode] 5.0s DCL.Friends.Tests.FriendsConnectivityStatusTrackerShould.RaiseOnlineEventWhenSameStatusIsRebroadcastAfterReset
  • [editmode] 5.0s CrdtEcsBridge.WorldSynchronizer.Tests.CrdtWorldSynchronizerShould.ThrowIfSyncBufferIsAlreadyRented
  • [editmode] 4.2s SceneRunner.Tests.SceneFacadeShould.ContinueUpdateLoopOnBackgroundThread(20,4000)
  • [editmode] 4.2s SceneRunner.Tests.SceneFacadeShould.ContinueUpdateLoopOnBackgroundThread(90,4000)
  • [editmode] 4.2s DCL.Tests.Editor.ValidationTests.SettingsAreValid
  • [editmode] 4.1s SceneRunner.Tests.SceneFacadeShould.ContinueUpdateLoopOnBackgroundThread(180,4000)
  • [playmode] 2.7s Global.Tests.PlayMode.CubeWaveSceneShould.EmitECSComponents
  • [playmode] 2.5s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.ContinuousTweensRunIndefinitelyWhenDurationIsZero
  • [playmode] 2.3s DCL.SDKComponents.Tween.Tests.TweenSequenceSystemShould.TextureMoveSequenceUpdatesMaterial
  • [playmode] 1.9s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.TextureMoveContinuousOffsetCompletesAndUpdatesMaterial
  • [playmode] 1.9s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.MoveContinuousMovesAndCompletesAfterDuration
  • [playmode] 1.9s DCL.SDKComponents.Tween.Tests.TweenSequenceSystemShould.TweenSequenceWithoutLoopCompletesOnce
  • [playmode] 1.9s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.RotateContinuousCompletesAfterDuration
  • [playmode] 1.5s DCL.SDKComponents.Tween.Tests.TweenSequenceSystemShould.TweenSequenceCompletesAllTweens
  • [playmode] 1.4s DCL.SDKComponents.Tween.Tests.TweenSequenceSystemShould.TweenSequenceWithMoveRotateScaleWithOmittedScale_ResolvesScaleFromCurrentTransform
  • [playmode] 1.4s DCL.AvatarRendering.AvatarShape.Tests.AvatarBaseLegacyAnimationPlayModeShould.ReplaceEmoteAnimation_DoesNotEnableAnimator_WhileLegacyAnimationIsPlaying

Full report: run summary · results + editor logs: editmode · playmode

Performance

🏁 Bare-metal benchmark finished — run #35605724255.

Full report

PR #10114, run #35605724255

Overall: ✅ no significant changes

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

How to read this table
  • Each build is measured 3 times, interleaved with the other build (change, baseline, change, baseline, ...) in the same session, so both see the same world content and machine state. The values are the median, and (min–max) is the lowest and highest of those runs.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a difference that passed every check: the runs are fully separated (every run of one build faster than every run of the other), and the median difference is at least 3% and at least 0.5 ms.
  • ⚪ within noise — the builds' runs overlap, or the difference is tiny; it cannot be told apart from random variation. Treat it as no change.
  • — informational — the 0.1% worst metrics average only the few worst frames of a run, so a single OS hiccup swings them by a lot; they are shown for context and never earn a verdict.
  • ⚠️ no verdict — the two builds' sessions were not comparable (very different sample counts, or too few usable runs), so no conclusion is drawn from them.
  • Exceptions per run — the average number of exceptions in a run's log, not counting teardown ones logged while the app quits. Flagged only on a difference of at least 2 per run and 1.5× the other build; exception kinds the baseline never threw are called out under the table. The Exception breakdown groups all of them by the explorer's report category and exception type (as totals across the runs).
  • A run that logged unusually many exceptions (at least 10 and 5× the median of its build's runs — e.g. a service was down during it) is excluded from all numbers and called out under the table.
  • The Overall line at the top only reacts to a metric that moved on two or more machines, or by 10% or more on one — a single modest 🟢/🔴 cell can still be a statistical fluke.

Apple M1

Metric Baseline Change Δ Result
Samples 4184 (×3) 4269 (×3)
CPU average 21.3 ms (21.1–22.3) 21.0 ms (20.5–21.7) -0.3 ms ⚪ within noise
CPU 1% worst 229.9 ms (229.5–232.0) 226.7 ms (216.1–231.1) -3.2 ms ⚪ within noise
CPU 0.1% worst 235.5 ms (233.4–236.9) 241.1 ms (233.2–241.7) 5.6 ms — informational
GPU average 34.2 ms (34.2–34.3) 33.0 ms (31.5–35.5) -1.2 ms ⚪ within noise
GPU 1% worst 44.6 ms (44.1–46.8) 44.7 ms (42.2–45.9) 0.1 ms ⚪ within noise
GPU 0.1% worst 45.8 ms (44.8–47.7) 46.0 ms (44.1–47.0) 0.2 ms — informational
Exceptions per run 0 0 0 ⚪ no significant change

Intel Core i5

Metric Baseline Change Δ Result
Samples 3530 (×3) 3793 (×3)
CPU average 25.4 ms (25.3–26.0) 23.5 ms (23.0–27.1) -1.9 ms ⚪ within noise
CPU 1% worst 814.9 ms (757.7–865.1) 709.5 ms (686.4–818.2) -105.4 ms ⚪ within noise
CPU 0.1% worst 856.3 ms (797.6–931.2) 851.6 ms (754.0–859.8) -4.7 ms — informational
GPU average 14.3 ms (13.8–14.5) 15.1 ms (13.1–17.1) 0.8 ms ⚪ within noise
GPU 1% worst 465.9 ms (390.9–480.9) 391.7 ms (382.5–610.5) -74.2 ms ⚪ within noise
GPU 0.1% worst 836.1 ms (790.3–918.1) 852.6 ms (752.1–861.3) 16.5 ms — informational
Exceptions per run 0 0 0 ⚪ no significant change

Automation

On demand — comment /visual-tests on this PR to run the visual regression suite against its build.

dalkia and others added 2 commits September 17, 2026 14:38
abgen publishes no LOD_0 - level 0 comes from the ISS path - and both the
descriptor's assets and the v49+ manifest that gates it are resolved by
the pipeline flip, so the LOD arg on its own leaves the client reading
production manifests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The "lods-base-url" flag variant and the --abgen-lods-base-url arg existed
to reach the LOD_1 prototype bucket. abgen publishes its LOD generation to
abgen-cdn beside the asset bundles now, so the only two states left are the
regular hosts and abgen - ResolveAbgenLodsUrl reduces to the shape
ResolveAbgenPipelineUrl already has.

Removes the arg, its AppArgsFlags entry and MainSceneLoader wiring, the
abgenLodsBaseUrl ctor parameter on both url sources, and the variant
constant. The flag-on test now pins AbgenLodsCacheKey outright, since the
resolved base is no longer configurable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@DafGreco DafGreco left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✔️ PR reviewed and approved by QA on both platforms following instructions playing both happy and un-happy path

Regressions for this ticket had been performed in order to verify that the normal flow is working as expected:

  • [ ✔️ ] Backpack and wearables in world
  • [ ✔️ ] Emotes in world and in backpack
  • [ ✔️ ] Teleport with map/coordinates/Jump In
  • [ ✔️ ] Chat and multiplayer
  • [ ✔️ ] Profile card
  • [ ✔️ ] Camera
  • [ ✔️ ] Skybox

Evidence:

20260918-1138-24.8852674.mp4

Player (33).log

Image

@dalkia
dalkia enabled auto-merge (squash) September 21, 2026 12:13
@dalkia dalkia self-assigned this Sep 21, 2026
@dalkia
dalkia disabled auto-merge September 21, 2026 17:50
@dalkia
dalkia enabled auto-merge (squash) September 21, 2026 17:50
@dalkia
dalkia merged commit 8983529 into dev Sep 21, 2026
35 of 38 checks passed
@dalkia
dalkia deleted the chore/abgen-lod branch September 21, 2026 17:52
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.

3 participants