Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Module data/loaders/progressive/pass-rollback

    Pass-start rollback for the progressive loaders (GSplats, Points, Lines, and the Mesh reveal ladder).

    A progressive loader's updateView appends each streamed level to loadedLODs as soon as the level's data arrives — BEFORE the caller has concatenated, projected and committed it. That ordering is deliberate (the concat at the end of the same call reads the array), but it means the ladder cursor advances on work that has not yet been proven committable.

    When the commit then fails, the consequences compound rather than back off:

    • the retry resumes from the ADVANCED cursor, so it loads the next level and attempts a strictly LARGER allocation than the one that just failed — escalation, not backoff, which is exactly wrong when the failure was RangeError: Array buffer allocation failed;
    • once the cursor reaches nLods, hasMoreLODs goes false. The refinement loop then drops the loader from anyHasMoreLODs, the RefinementFailureTracker never reaches its cap, no "giving up" toast fires, and the node is stranded PERMANENTLY at its last successfully committed rung while the data-loading monitor reports the cursor as "LOD n/n ~100%".

    Observed on the hosted cosmicflows_laniakea demo (#2426): basins reported a complete ladder while rendering a coarse prefix, and the retries that were supposed to recover the node were the thing driving it further out of memory.

    The fix is to make a failed pass leave the loader exactly as the pass found it, so a retry re-attempts the SAME prefix. Each loader records the level count at pass start and exposes a rollbackToPassStart() that its refinement wrapper calls from its catch. The DECISION lives here as a pure function so the four loaders cannot drift — the same reason streaming-policy.ts owns the streaming discipline — while the mutation stays in the loader, which is the only thing that can touch its private fields.

    COUNTS HERE ARE LOGICAL LEVELS, NEVER PAYLOAD ENTRIES. Every loader now FOLDS its ladder — the rungs are concatenated into one cumulative payload and the parts released — so loadedLODs.length becomes 1 while the logical count is still 7. That is why each loader carries a separate _loadedLODCount, and why LadderRollbackState takes the two counts apart rather than inferring either from an array length. Feeding a payload count into a logical field is silently wrong: no throw, just a memo gate evaluated against the wrong number.

    LadderRollbackState
    LadderRollbackPlan
    planLadderRollback
    tryRollbackToPassStart