Skip to content

[BUG] IL2CPP custom NPC portraits disappear after returning to menu and reloading a save #356

Closed

Description

@ifBars

Summary

On IL2CPP game 0.4.7f11, custom NPC portraits are available on the first save load but are missing after quitting to the menu and loading the save again. This was reproduced with PR #353 alone, without either NPC lookup optimization (#354 or #355).

Environment

  • Schedule I beta 0.4.7f11, Steam build 25770926, updated and validated with SteamCMD.
  • S1API PR fix: keep packaging-content patches working on game 0.4.7f11 #353 exact head: 8c22c42e2a3703320fc6af58424d0109957cbb84 (beta.8 lineage plus the packaging compatibility change).
  • IL2CPP, MelonLoader 0.7.3, fresh f11 interop wrappers.
  • Isolated game installation with S1API and a disposable minimal probe registering four automatic custom NPC roles: plain, customer, dealer, and supplier.
  • Copied save fixture originally created on 0.4.6f8 Alternate. A newly created f11 save was not tested.

Steps to reproduce

  1. Register one automatic custom NPC for each of the four roles in a minimal test mod.
  2. Start the IL2CPP game and load the copied save. Wait until Main is loaded and normal gameplay is available.
  3. Check that all four custom NPCs have portraits.
  4. Quit to the main menu without restarting the game process.
  5. Load the same save and check the four portraits again.
  6. Repeat the menu/reload cycle once more.

Expected behavior

All four custom NPC portraits are available after each load, as on the first load.

Observed behavior

All four have portraits on load 1. All four report missing portraits on loads 2 and 3, with eight portrait capture errors across those reloads. Other probe observations remain stable: four NPCs, expected voice, walk speed, renderers, contacts, and PoI membership.

Representative retained state markers:

NPC|Load=1|Id=s1api_pr_customernpc|Walk=1.2|Voice=Hippie VO|Portrait=True|Pois=1|Circles=1|Objects=36
NPC|Load=2|Id=s1api_pr_customernpc|Walk=1.2|Voice=Hippie VO|Portrait=False|Pois=1|Circles=1|Objects=36
NPC|Load=3|Id=s1api_pr_customernpc|Walk=1.2|Voice=Hippie VO|Portrait=False|Pois=1|Circles=1|Objects=36

Controls and investigation limits

The #353-only control and the #353+#354+#355 integration produce identical per-NPC state reports and the same eight portrait errors. The Mono control on 0.4.7f11 Alternate (build 25770882) retains portraits through all three loads and has zero portrait errors.

This establishes reproduction without the optimization PRs. It does not establish the root cause or the first affected S1API version. The latest published prerelease was not separately exercised for this portrait scenario. Complete raw capture logs and the disposable probe were removed after validation; the counts and state markers above were retained.

Acceptance criteria

Reproduce and identify the responsible reload/capture lifecycle path, then verify that all four roles retain usable portraits through three same-process save loads on IL2CPP. Keep the passing Mono behavior intact.

Activity

  1. added
    bugSomething isn't working
    npcsNative game NPC system
    betaA game update on the beta & alternate-beta steam branches
    MediumEdge case bugs, etc
    on Oct 8, 2026
  2. ifBars commented on Oct 8, 2026

    @ifBars
    OwnerAuthor

    @Diffuin Investigate this. Make sure your assembly copies are up to date. Find out why this would happen. Also look at code archiver commits for recent beta update changes related to this.

  3. diffuin commented on Oct 8, 2026

    @diffuin

    Issue investigation

    Confidence: Low

    The retained errors place the failure in portrait capture or validation, before icon assignment. An unusable render rig after reload is the leading hypothesis, but the initiating cause remains unproven. A missing-avatar path demonstrably skips the capture callback and can produce the reported error after a timeout. Assembly freshness could not be verified or refreshed: no game assemblies or IL2CPP wrappers were available, and the local beta archiver ends at f10.

    Recommended next steps

    1. Keep any eventual fix within the existing capture adapter and lifecycle seam. Preserve explicit-icon precedence, public APIs, NPC identities, and save/network formats.
    2. Retain serialized capture and the existing Update plus end-of-frame wait. Session portrait caching would bypass the failing path; [FEATURE] Loading a save with many custom NPCs repeats scene and assembly scans per NPC (multi-second stalls) #351 also records a withdrawn caching approach that missed clothing restoration.

    Open questions

    • Do failing reload captures time out without a callback, return null, or deliver textures rejected by coverage validation?
    Evidence and validation

    Evidence inspected

    • The affected portrait adapter was inspected at exact head 8c22c42. Its AvatarCompatibility blob matches the current GitHub beta file, 906b3900110f0d0ee2d1d891b91da893a10afd85. The live path uses an S1API camera coroutine; the older singleton capture implementation is disabled.
    • Main unload calls ResetMugshotState and ResetRendering. The latter advances RenderingEpoch, stops the tracked capture, destroys the cached fallback rig, and releases ownership. Therefore a simply forgotten reset is unsupported unless cleanup fails before reaching those calls. The incomplete-portrait error combines callback timeout, null texture, and rejected pixel coverage, so the retained error counts cannot distinguish them.
    • The available beta archiver comparison, 048d8f9 (0.4.6f12) to 36f8f28 (0.4.7f10), removes AvatarFramework.MugshotGenerator, adds Avatar.Tools.MugshotGenerator, and moves appearance operations into AvatarAppearance. These are relevant adapter changes, but stripped declarations provide no controlling method bodies or f11 comparison. AssetRipper retains an inactive Main/MugshotRig object, but its version does not establish f11 rig composition.
    • Issue [FEATURE] Loading a save with many custom NPCs repeats scene and assembly scans per NPC (multi-second stalls) #351 contains earlier f9/f10 reload-failure reports. Its initial claim that optimization changes restored portraits was superseded by later comments and the [BUG] IL2CPP custom NPC portraits disappear after returning to menu and reloading a save #356 control showing identical failures with and without those changes. This weakens an f11-only origin hypothesis while leaving the first affected version unverified.

    Validation performed

    • Read repository guidance and inspected exact-head NPC creation, capture, coverage validation, icon assignment, and Main-unload cleanup.
    • Compared locally available archiver commits offline and checked current beta adapter content through read-only GitHub tools.
    • Inspected bounded AssetRipper scene evidence without copying or redistributing proprietary artifacts.
    • Verified the checkout remains unchanged. Builds and unit tests were not run because dotnet and native references are unavailable.

    Runtime validation remaining

    • Verify f11 assembly provenance against Mono build 25770882 and IL2CPP build 25770926, including freshly generated wrappers. Obtain the corresponding archiver snapshot for a focused f10-to-f11 comparison.
    • Inspect one failing IL2CPP reload capture first. Record RenderingEpoch, generator and avatar identity, scene and hierarchy activation, callback completion, exception details, and pixel coverage before and after resizing. A valid active avatar with a completed callback and rejected pixels would falsify the missing-avatar diagnosis and redirect investigation to rendering or framing.
    • After that targeted capture is correct, verify all four roles through three same-process loads on IL2CPP and Mono, using both the copied fixture and a newly created f11 save. Check usable portraits and restored clothing. Run matching repository builds and contract suites when references and tooling are available.
    • Create a pull request to implement this plan and close this issue when merged.
    Diffuin run details
    • Provider: codex
    • Model: gpt-6.1-sol
    • Reasoning: high (Luna advisor: complexity (high); baseline source-backed technical request)
    • Elapsed: 294s
    • Codex thread: 01a119d9-bcf7-7530-912e-2394bd0c58b3

    AI notice: Generated with AI assistance and not guaranteed accurate. Verify findings and plans against the current source and runtime.

  4. ifBars commented on Oct 8, 2026

    @ifBars
    OwnerAuthor

    The f11 reload reproduction now isolates a pose failure: callbacks complete, but the cloned render avatar's head moves from about 1.62m to 1.25m during the settled frame. Visible portrait pixels drop from roughly 76,500 to 3,585, so coverage validation correctly rejects the image. Hip position and appearance scale remain unchanged.

    A disposable diagnostic that disables gameplay animation on the owned fallback clone and rebinds/freezes its Animator preserves the upright portrait on first load and reload: all eight captures have 78,184 visible pixels and all four icons remain available. The known-good 410b552 capture flow supports keeping serialization and Update plus end-of-frame settling intact.

    I will make that reset specific to the owned fallback mugshot rig, preserving workbench preview animation, live NPC animation, appearance application, explicit icons, and spawn/finalization order. Then validate the production change separately on Mono and IL2CPP before opening the fix PR.

  5. ifBars commented on Oct 8, 2026

    @ifBars
    OwnerAuthor

    The larger reproduction found why creation-time freezing was incomplete: native Avatar.Awake caches the live source's transient hip position; Avatar.Update restores it on the cloned rig during the settled frame. At #358 head 6052288, the owned clone records its rebound hip position so Update keeps the upright pose. Serialized capture and the frame wait are preserved.

    With #359 and optimization PRs #354/#355 included, both runtimes pass 32 NPC types over two same-process loads: 64 nonblank captures per runtime, matching saved/current hips to four decimal places, and one native NPC component per object. Controlled gender/weight capture checks also preserve the native blend-shape values. The workbench's inherited disabled-animator regression was reproduced and repaired on its separate preview clone.

    Fresh IL2CPP co-op reload/rejoin is still an unresolved validation limit; real Steam transport is untested. PR #358 remains open with green hosted checks. No merge or release was performed.

  6. ifBars commented on Oct 9, 2026

    @ifBars
    OwnerAuthor

    Fixed by #358 and shipped in 3.2.1-beta.9.

    The fallback clone now records its rebound hip position in the native saved-position field, so the next Avatar.Update retains the capture pose. Captures remain serialized with the existing settled-frame timing. The separate workbench clone has its animators enabled for pose controls.

    On game beta 0.4.7f11 with MelonLoader 0.7.3, the final combined source generates 64 nonblank portraits from 32 custom NPC types over two loads on each runtime. Controlled gender/weight captures and preview pose checks pass on both runtimes. The final beta.9 DLLs also retain nonblank portraits on both peers during first load and same-process multiplayer reload/rejoin, with normal gameplay readiness checked. Closing as completed; real Steam transport remains untested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    MediumEdge case bugs, etcbetaA game update on the beta & alternate-beta steam branchesbugSomething isn't workingnpcsNative game NPC system

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions