Skip to content

perf: index scene objects instead of scanning for every custom NPC - #354

Merged
ifBars merged 4 commits into
ifBars:betafrom
r-melvin:perf/scene-object-index
Oct 9, 2026
Merged

ifBars merged 4 commits into
ifBars:betafrom
r-melvin:perf/scene-object-index

Conversation

@r-melvin

@r-melvin r-melvin commented Oct 7, 2026 •

Copy link
Copy Markdown

Refs #351

Summary

Custom NPC creation repeatedly scans loaded scene objects for movement defaults, map PoIs, and contact circles. This PR indexes those lookups per scene while reading movement values and UI activation live.

The maintainer follow-up preserves the original lookup contracts:

  • Validate all indexed keys before returning a populated bucket, including objects re-keyed from another bucket or assigned an ID after the snapshot.
  • Invalidate membership when native NPCPoI.SetNPC or RelationCircle.AssignNPC assigns a new object, so a late second object under an existing NPC ID is found.
  • Retain inactive PoIs in the snapshot and filter their current activation at lookup time. Disabling and reactivating a PoI preserves the original active-only results.
  • Read native runtime identity data null-safely. An unrelated circle or PoI whose NPC data is not initialized cannot abort the entire index; its later ID is detected by snapshot validation.
  • Release cached references on scene reset. Misses still rescan; movement values remain live.
  • Keep voice-database discovery uncached. A duplicate loaded database name must still raise the existing ambiguity error immediately.

NPC spawning, finalization, appearance capture, and portrait timing are unchanged.

Compatibility

Source and binary: no public/protected API changes; the index and lifecycle notifications are internal. Behavior: preserve lookup order, movement defaults, active-only PoIs, late UI membership, and voice ambiguity checks. Persistence/networking: stable IDs, save formats, payloads, and host authority are unchanged; indexes are local to each peer. Mono and IL2CPP use the same implementation and matching native assignment hooks.

Validation

SteamCMD updated both local beta copies to game 0.4.7f11: alternate-beta Mono build 25770882 and beta IL2CPP build 25770926. Fresh IL2CPP interop wrappers were generated from that installation. Builds used isolated worktrees, explicit native reference paths, and automatic deployment disabled.

Source MonoMelon Il2CppMelon
Current PR head (beta merged, includes #353) 788/788 762/762
#353 + this PR + #355 integration 792/792 766/766

The separate PR builds have zero warnings/errors. Five additional scene-index regression tests cover populated-bucket re-keying, formerly unkeyed objects, late membership notifications, unchanged membership reuse, and activation filtering without another scan.

Live integration: four automatic physical NPCs (plain, customer, dealer, supplier), one copied completed save, three load/quit-to-menu cycles per runtime. All 12 NPC reports per runtime preserved active state, movement speed 1.2, Hippie VO, renderer count, and applicable contact/map membership. Native probes passed populated-bucket re-keying, late duplicate circles/PoIs, PoI disable/reactivation, and late duplicate voice rejection.

Mono host + joining client: passed using separate isolated installs and verified GSE identities. Both peers observed the same two-member native lobby. The host started server/client networking; the joining client started client networking only and followed the host's save load. Both passed four-role NPC state, cache mutations, and custom packaging checks.

IL2CPP host passed the same checks. Its joining client reached Main but failed the strict four-NPC gate: native Customer.Awake throws and a dealer ID is duplicated, leaving five custom NPC entries. A matching #353-only control reproduces the same exception, duplicate ID, and terminal failure with neither optimization installed. This is an existing baseline limitation; healthy IL2CPP four-role co-op has not been established by this validation.

Compared the final integration with #353 alone using the same four-role probe and copied save: all 12 per-NPC state reports match on each runtime. Mono has no portrait-capture errors. IL2CPP has the same eight incomplete-portrait errors on reloads in both builds, with missing reload mugshots; this existing baseline problem remains. Native custom packaging passes in both the #353-only control and the final integration.

This is a behavior and scan-reuse validation with four custom NPCs, not a full mod-pack loading-speed benchmark. Disposable probes, saves, game assemblies, and screenshots are excluded from the commit.

CI repair

Merged current beta into this PR without rewriting its history. This incorporates #353's f11 packaging fix; the merge adds only ProductPackagingContentPatches.cs relative to the previous PR head. Current head: 43154c1edf1b3c01c1651b07c5c6171f792fa310. No CI workflows were changed.

Local validation on this head: 788/788 MonoMelon and 762/762 Il2CppMelon tests, zero build warnings/errors, f11 references, automatic deployment disabled. Both hosted workflows pass on this head: IL2CPP build, Mono contract tests, documentation build and coverage, and API compatibility. Deployment is skipped.

The separately reproduced IL2CPP reload-portrait and joining-client failures are tracked in #356 and #357. Existing gameplay evidence above remains applicable; this CI repair adds the same #353 dependency used during that validation.

Movement presets, voice databases, NPC map PoIs and contact circles were each
found with a scene or asset scan per custom NPC. They now come from a per-scene
index built from one scan. Only found objects are reused, and only while alive
and still under the key they were indexed by; a lookup that finds nothing scans
again, so an object created later during loading is still found. Indexes clear
on scene change.

Refs ifBars#351

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

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dd97f23f-556b-466e-bec9-b26e496c85be

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@ifBars ifBars left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed at daa4836.

The maintainer follow-ups address demonstrated lookup regressions: re-keying into an existing bucket, late native UI membership, PoI disable/reactivation, uninitialized native NPC identity data, and duplicate voice-database ambiguity. Voice lookups remain uncached; spawning, finalization, and portrait timing are unchanged.

Local validation with #353 as the f11 compilation dependency: 788 Mono / 762 IL2CPP tests, zero warnings/errors. Integration: 792 / 766 tests. Four roles over three save loads produce the same 12 NPC reports as the #353-only control on both runtimes. Mono two-peer gameplay/cache/packaging validation passes.

Remaining gates: fresh f11 CI fails on the existing packaging GenerateIcons references, so #353 must land first. Healthy IL2CPP four-role client validation is still unavailable: both this integration and #353 alone reproduce native Customer.Awake failure and a duplicate dealer ID (five entries). Both also reproduce the same eight reload portrait errors. These baseline problems belong in separate follow-up work, not in this optimization PR. A large mod-pack speed benchmark has not been established.

Leave this PR open and revalidate after the packaging dependency lands.

@ifBars

ifBars commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Follow-up validation with fixes #358 (head 6052288) and #359 (349ad10) included: 32 custom NPC types pass two same-process loads on both Mono and IL2CPP. Each runtime produces 64 nonblank portraits, retains all icons, and has one native NPC component per object. The owned mugshot rig's saved/current hips agree to four decimal places. Workbench stand/crouch/return-to-stand animation and controlled gender/weight capture checks also pass.

The initial portrait freeze alone was incomplete: the larger probe exposed a transient hip cached by native Avatar.Awake and restored by Avatar.Update. #358 now corrects that saved position at clone creation; capture timing and serialized ownership remain intact. The optimization combinations changed when this existing lifecycle hazard appeared, so the earlier blank images are not evidence that either optimization directly caused it.

The combined final source passes 792 Mono and 766 IL2CPP contract tests. Both fix PRs have green hosted checks and remain open. Fresh IL2CPP co-op reload/rejoin is still inconclusive, real Steam transport is untested, and baseline client Contacts warnings remain. These checks establish compatibility in the tested matrix; they do not establish a repeatable loading-time improvement or an unconditional absence of regressions.

@ifBars
ifBars merged commit 821ee26 into ifBars:beta Oct 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

beta A game update on the beta & alternate-beta steam branches

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants