Skip to content

fix startpos cold loads on maps with dust, clutter, and lightning - #140

Open
Microck wants to merge 4 commits into
mainfrom
fix/startpos-dust-noise-recreate
Open

fix startpos cold loads on maps with dust, clutter, and lightning#140
Microck wants to merge 4 commits into
mainfrom
fix/startpos-dust-noise-recreate

Conversation

@Microck

@Microck Microck commented Aug 26, 2026

Copy link
Copy Markdown
Owner

what

startpos slots on several maps wouldnt load after you left the map and came back. this started as one bug (dust bunnies) and turned out to be a stack of them, each hidden behind the last: the token ceiling from #138 was refusing these loads before they could reach any of this. this PR fixes the whole stack that a 6-map verification protocol surfaced.

the layers

each fix exposed the next one, so they're listed in the order they were found:

  1. dust noise textures. DustEdges builds dust-noise-a/b lazily in BeforeRender. the fresh baseline a cold load pairs against is never rendered (null slot, no structural path) and exiting the map disposed the captured level's instances out of VirtualContent. every lookup missed and the slot was refused. the restore now has a last-resort recreate step for an exact allowlist of runtime texture labels, bounded by a pixel budget, authenticated by the same key comparison the detached lookup uses. capture never recreates anything.
  2. clutter blocks (celestial resort). generated ClutterBlocks cross-reference each other through HashSets. hash entry positions belong to the saving process, so structural keys now wildcard _entries indices the way they already wildcard List._items, and vanilla runtime entities get the same owned-state licence generated mod entities had (a surplus block carries its own Hitbox).
  3. dust blink + eyeballs. DustGraphic builds a blink coroutine and an Eyeballs entity when dust is on camera. neither is in any component list, so nothing could vouch for them. a component built on first use and kept in a declared field now restores into its proved owner (never when IDisposable, never over a fresh object), and a runtime entity keeps its captured reference to the fresh component that built it.
  4. lightning bolts (farewell). LightningRenderer's Bolt runs its own coroutine by hand. a saved mid-flight routine against a fresh bolt whose routine already finished has no fresh occurrence to spend, which is expected silence, not missing evidence. an owner-proved iterator's canonical stack edge is now licensed, and owned-nested-state objects count as iterator owners.

all of these are pre-existing (the resolver code was untouched since beta 70); the token-cap fix just made them reachable.

test plan

  • in-game protocol on the test box, per map: set 3 slots in different rooms, warm load each, exit the map, re-enter, cold load all 3 and confirm each lands in its exact room. ran on celestial resort (clutter + dust), summit, farewell (lightning), SC2020 advanced Cookie, strawberry jam hyperlife, and 9D resort d-side (dust on a custom map). 36/36 checks pass.
  • full scripts/akron-verify/run.sh harness at 0 failed checks on the final build (cross-room/cross-map/restart pixel-hash equality, load latency, log gates).
  • 1808 unit tests green. new coverage: dust-texture recreate round trip + wrong-key/portable-key/oversize refusals, hash-entry-shifted clutter graphs, the blink component round trip + disposable refusal, the eyeballs captured-fresh edge, and a mid-flight bolt routine restoring into a finished fresh one.
  • reproduced the original refusal byte-for-byte on the box before fixing, and re-reproduced each deeper layer the same way.

known limits found along the way

  • SC2020's advanced heartside first room holds a LuaCutscenes entity with a live lua interpreter. that's a process pointer, so Set refuses it loudly by design ("Set it again") - a lua vm is not persistable.
  • the same map also crashes the wine test box on load. environment problem, not akron; it needs a native windows check at some point.

planning and implementation with Claude (Fable), reviewed and tested by me on the remote test box.

DustEdges creates dust-noise-a/b in BeforeRender, so a never-rendered
fresh baseline holds null at the anchor owner field, and exiting the
map disposed the captured level instances out of VirtualContent. A
cold load of a slot saved near dust bunnies then failed every lookup
and refused the slot. Restore now recreates the equivalent wrapper as
a last resort: only for label keys, only after the fresh index, the
detached registry, and the structural path all miss, and never at
capture. The changelog contract guard now checks the newest contract
mention instead of demanding one in every active section.
@Microck
Microck deployed to release-build August 26, 2026 21:38 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7ae61076-73c0-45f3-8ff9-2529eebb667c

Warning

Your free Security trial is over. An organization admin can activate billing to continue.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a77a97799

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +9848 to +9850
string name = resourceKey.Substring(0, dimensionsSeparator);
if (name.IndexOf('/') >= 0 || name.IndexOf('\\') >= 0) {
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict recreation to known runtime texture labels

When a lazily loaded file-backed texture has a bare path such as icon.png, this separator check classifies the path as a runtime label. GetLiveResourceKey accepts any nonempty VirtualTexture.Path, and all virtual assets are marked non-portable, so if the fresh graph and detached registry miss that texture during a cold load, this branch creates a transparent texture and reports a successful restore instead of preserving or refusing the real content. Restrict recreation to the known dust-noise labels or carry explicit capture-side evidence that the texture was data-backed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

fair. recreation is now an exact allowlist (dust-noise-a/b only), so a bare file path like icon.png keeps the refusal. new populations get added on measurement instead of guessed from the key shape. 82b2ada

Comment on lines +9853 to +9859
// 16384 is the largest texture current GPUs accept, and a real
// runtime-built texture is a fraction of the screen; the cap keeps a
// hostile snapshot from demanding the allocation instead.
if (dimensions.Length != 2 ||
!int.TryParse(dimensions[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out int width) ||
!int.TryParse(dimensions[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out int height) ||
width <= 0 || height <= 0 || width > 16384 || height > 16384) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound recreated textures by allocation size

When a snapshot is corrupted or edited to carry a valid runtime-texture key such as dust-noise-a|16384x16384, this guard permits an approximately 1 GiB RGBA allocation, and distinct keys can request several such textures during one restore. Thus the newly added hostile-input guard can still exhaust system or GPU memory and crash the game; validate the width-height product against a small byte budget appropriate for generated textures rather than using only the GPU's maximum dimension.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

done, the guard is now a pixel budget (512x512, 1 MiB rgba) instead of the gpu max per axis, so a doctored key cannot demand the allocation. 82b2ada

A bare file path is indistinguishable from a made-up name in the key,
so recreation is now limited to the measured dust-noise labels, and
the dimensions are bounded by pixel count instead of GPU maximums so
a doctored snapshot cannot demand a 1 GiB allocation. Both from PR
review.
@Microck
Microck deployed to release-build August 26, 2026 22:10 — with GitHub Actions Active
Cold loads that survived the dust-noise fix still refused rooms whose
saved graphs hold state a clean room load never builds. Five licences
close what the six-map verification protocol surfaced:

- hash-container entry indices (HashSet/Dictionary _entries) wildcard
  in structural keys like list indices do; they are per-process facts
  and Celestial Resort clutter graphs run through them
- vanilla runtime entities get the generated-entity owned-state
  licence, so a surplus clutter block carries its own collider
- a Component built on first use and kept in a declared field no
  ComponentList carries restores into its proved owner (DustGraphic
  blink, Bolt routine), never when disposable and never over a fresh
  object
- a runtime entity keeps its captured reference to the fresh component
  that built it (DustGraphic Eyeballs)
- an owner-proved iterator canonical stack edge is licensed when the
  fresh routine already finished, and owned-nested-state owners count
  as iterator owners (LightningRenderer bolts)

Verified in game per map on the test box: 3 slots in different rooms,
warm load each, exit, re-enter, cold load all 3 - Celestial Resort,
Summit, Farewell, SC2020 Cookie, SJ hyperlife, 9D Resort D-side, 36/36.
@Microck
Microck deployed to release-build August 27, 2026 01:26 — with GitHub Actions Active
@Microck Microck changed the title fix startpos cold loads on maps with dust bunnies fix startpos cold loads on maps with dust, clutter, and lightning Aug 27, 2026
@Microck

Microck commented Aug 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 334cb0b95c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +10006 to +10010
string[] dimensions = resourceKey.Substring(dimensionsSeparator + 1).Split('x');
if (dimensions.Length != 2 ||
!int.TryParse(dimensions[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out int width) ||
!int.TryParse(dimensions[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out int height) ||
width <= 0 || height <= 0 || (long) width * height > MaxRecreatedTexturePixels) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound the total recreated texture allocation

Fresh evidence after the prior per-texture fix: the allowlist constrains only the label, while arbitrary distinct dimensions such as dust-noise-a|512x512, dust-noise-a|511x513, and many more all pass this check. A doctored .akr can include many such anchor nodes, causing up to 1 MiB to be allocated for each before graph validation completes; VirtualContent retains those textures even if a later node makes the restore fail, so this can still exhaust memory. Require the known 128x72 dimensions for these two labels or enforce a cumulative recreated-pixel budget for the entire restore.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

good catch, the label alone still let made-up sizes mint distinct keys. the table now pins name and size together (DustEdges hardcodes 128x72 for both), so one process can only ever materialize those two wrappers, and a repeat key reuses the registered one. 2f4e380

The allowlist bounded the label but not the allocation: distinct
made-up sizes each minted a fresh key and a fresh 1 MiB texture, and a
failed restore leaves them registered. DustEdges hardcodes 128x72 for
both noise textures, so the table now pins name and size together and
one process can only ever materialize those two wrappers. From PR
review.
@Microck
Microck deployed to release-build August 27, 2026 01:51 — with GitHub Actions Active
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.

1 participant