Files
GaCy-GO/docs/GACHA-NATIVE-ADAPTER.md
2026-10-28 18:54:43 +07:00

13 KiB

Native per-character Warp adapter

Current Client observation

The enabled presentation configures 76 five-star banners, including collaboration characters. Sending all synthetic IDs in the native GetGachaInfo response caused GachaModule._CmdGetGachaInfoScRsp to throw in the running Client. The server now respects mapped_banner_preview_count (currently zero) even when presentation is enabled; the adapter registers mapped pools separately. The user confirmed Warp opens again, and the Client logged PAGE_BIND ready=76 configured=76. This confirms pool binding, not successful draws or a finished visual layout. Earlier release-gate notes below describe historical checkpoints, not current policy.

Incremental projection, 2026-10-27

Early opening could previously freeze the native collections after only a few of the configured banners had registered, leaving the remainder invisible until the page closed. The adapter now registers at most two configured models per tick and adds at most one missing tab per tick while Warp is open. It checks the page's existing group objects before appending and does not call the native _refresh or select another tab. The latter resets selection.

The append sequence follows the single-pool branch of the current Client's GachaMainPage._create_pools_panel: load prefab, create/bind panel, set its pool and timeout callback, add it to TabControl, then set up its group button. The new content root is inactive until selected. The exact current baked modules were captured read-only; Ui.Component.TabControl.add_item_created does not reference the current selection field. Existing page arrays track the panel for normal disposal. If the native add operation partially fails, there is no proven individual-button removal API; PAGE_APPEND_ERROR reports the tracked failure, and normal page disposal cleans it. Failure to load one supporting four-star portrait leaves the other content visible. Registration failures retry on a later bounded pass rather than permanently suppressing a banner.

This does not replace the native page's initial _create_pools_panel lifecycle: if all pools are already registered before the page opens, that native function may still instantiate those panels synchronously. The new incremental path, its rollback behavior, selection preservation and performance need Client confirmation; Lua parsing alone does not establish those outcomes. Experimental loader scripts have been removed from scripts/ and Frida detached.

Client rank/item coverage — 2026-10-27

The captured AvatarConfig.json and current imported roster contain 76 five-star avatars. Five had previously been excluded because the older external Eidolon resource did not contain their rank rows. Read-only, bounded lookups of the exact AvatarRankConfigExcelTable.GetData rows for those avatars' captured RankIDList yielded 30/30 rows and their UnlockCost; the five distinct cost items were independently retrieved from the current Client's ItemHelper and their ItemRows verified. The companion asset audit also found each avatar's head icon, draw-card splash and result image, and a matching avatar ItemData.

tools/merge_captured_eidolon.py joins the captured IDs to AvatarConfig, checks all six ranks and cost ItemRows, records capture hashes and preserves existing Eidolon entries. The stable-ID generator then retained all 94 earlier identities and added five; all 76 imported five-star avatars now have an active mapping. The custom IDs remain sandbox identifiers, not official banner IDs. These changes did not alter a player's save, inventory, rank, EX state or pity.

Evidence is retained in private battle_diagnostics/ capture logs; the SHA256 digests and captured Client identity are recorded in EidolonConfig.json. The five added banner entries still need native Client registration and draw confirmation after the integrated build is deployed. First acquisition of an avatar absent from the imported roster remains outside this sandbox flow.

Current status — supersedes historical gate notes below

The enabled implementation was built and deployed on 2026-10-25. The Client registered all 71 eligible rarity-five character banners. The user confirmed the prototype's splash art and native panel placement after the opaque overlay was removed. The implementation reuses the generic installed prefab and changes only its owned content; these are custom banners, not restored official assets.

The latest source adds three featured four-star portraits, saved pity labels and the user's requested hard guarantees: featured five-star by 80 pulls and four-star-or-higher by 10 pulls. Shared limited pity and inventory awards commit atomically. Three-star Light Cones come from the captured rarity catalog. The three four-star companions rotate over the imported rarity-four roster; they are sandbox lineups, not captured official banner companions. Base rates are configurable sandbox defaults and are not represented as captured Beta odds.

The 94 stable mappings consist of 71 advertised rarity-five banners and 23 reserved rarity-four mappings. Five avatars lack the required Eidolon conversion config. Registration is confirmed in Client logs; latest portrait layout, draw results/pity persistence, and entering/exiting every new banner have not been confirmed after the latest deployment. AA loading repair is the current gameplay blocker. No tests were added or run for this continuation.

The sections below retain earlier audit checkpoints. Their OFF gate and unproven ItemHelper notes describe those earlier states, not the current policy.

Confirmed foundation

The previous uninitialized-pool approach is superseded. On 2026-10-25 the initialized StandardGachaPoolData / Row / SingleDrawData / MultiDrawData clone was registered through _GachaPoolDatasThisVersion and the native _AddGachaPoolDataToGroupDict using the five-argument reflection Invoke overload with boxed BindingFlags. The Client reported pool=900001 pools=3 groups=3.

Evidence: battle_diagnostics/runtime-continuation-metadata-20261025.log, [GAC_GACHA_CLONE_REGISTER]. Registration proves native lookup/group creation, not correct artwork or draws.

Implementation

  • internal/gameserver/gacha_presentation.go validates a policy-selected installed generic template and builds the presentation payload from the stable banner mapping and AvatarConfig.json.
  • internal/gameserver/gacha_presentation.lua is embedded behind the policy, outside scripts/. It clones initialized models, assigns the mapped avatar's runtime AvatarName, uses the configured avatar icon, registers native single-pool groups, and hooks only the custom tab's content rendering.
  • The new card uses AvatarCutinFrontImgPath. AvatarCutinImgPath denotes a different asset family; do not derive a DrawCard path from that field.
  • Art loading uses the native BasePanel _async_load_sprite_to helper captured in .runtime/named-baked-capture/gacha-avatar-panel.json.
  • The opaque card is local to the mapped tab's known node_content binder. It does not hide a global Canvas, world camera, phone menu or HUD.
  • Registration and installed-asset checks are bounded to two entries per render interval. Actual native group keys are retained for rollback; no group-ID arithmetic or invented enum is used.
  • Turning the policy off removes owned clone references after Warp closes. A currently open custom card is not removed mid-draw.
  • The same mapping is accepted by DoGacha; when native presentation is enabled, GetGachaInfo advertises the full active mapping as well.

Current release gate

resources/GachaSandboxPolicy.json has native_presentation.enabled=false. Do not turn it on solely because the Go binary compiles. Verify the single-banner card's constructor/binder, displayed avatar art/name and draw 1/10 behavior in the Client first. No tests were added or run.

There are 94 mapped avatars. Five imported avatars have no configured first Eidolon conversion and remain outside this sandbox mapping. First acquisition of an absent imported avatar is still rejected. This is a custom featured sandbox presentation, not restored official limited prefabs.

Controlled runtime tools

tools/build_gacha_presentation_probe.py --output <file> renders one banner by default. It never stages a script, changes policy, or writes a save. --limit 94 --assets-only emits only a bounded AssetMgr.Exists capture under a separate global configuration name; it does not register pools or UI.

tools/gacha_item_data_probe.lua reads exact ItemData constructor and ItemHelper.GetItem metadata. The ItemHelper return type needs confirmation before relying on it as a native ItemData array element. The adapter now resolves the exact static GetItem(UInt32) reflection overload and rejects a return type that is not assignable to the captured native ItemData array element. It then checks the actual returned instance and ConfigID before mutating native pool collections. This is a runtime guard, not evidence that the current Client has that overload.

The staged one-banner adapter is a runtime adapter with an onBeforeRender callback. When finished, close Warp and send _G.__GAC_GACHA_PRESENTATION={enabled=false} to remove owned clones, then remove the staged file. A production heartbeat with the disabled policy supplies the same cleanup signal.

Integration

The caller appends loadGachaPresentationLua() to PlayerHeartBeat.DownloadData, logging a presentation error without failing unrelated heartbeat callbacks. The function reads external resource policy, so an operator can disable it without replacing saves or executables.

Unknown at this checkpoint: the new card has not yet been Client-confirmed; callback failure must not be described as a working banner. Native registration is confirmed separately from presentation, asset availability, and successful draws.

Offline adapter audit, 2026-10-25

These source changes were made without staging a probe, changing policy, running tests, rebuilding or restarting servers:

  • Rendering now destroys the newly created opaque card if a later binding or sprite-request call throws. The previous path logged the exception but retained an untracked overlay. This covers synchronous setup failure; it does not prove the later asynchronous sprite request succeeds.
  • Disposal explicitly destroys the owned card. No global Canvas, camera or unrelated page object is touched.
  • Rendering hooks match the exact owned pool instance as well as its configured ID. A foreign extension's pool with the same ID must not be repainted as this adapter's character.
  • Each hook captures its original callback locally. Replacing the mutable originals table on a later policy revision can no longer make a retained older wrapper delegate recursively into a newer wrapper.
  • Native pool/group collections are frozen while the Warp page is open. Configuration changes, disabling, and registration reconciliation wait for the page to close; the selected pool is not removed during a draw.
  • A new configuration digest removes the previous owned projection before installing a replacement. Cleanup also removes the exact owned group from the native cached group list and clears a selected-pool cache only when it refers to the removed clone. It does not remove unrelated groups or derive native group keys.
  • Re-registration after a native GetInfo refresh removes that banner's stale owned references before creating its replacement. Registration rollback also removes newly created cached group entries.
  • The Go payload and controlled-probe builder include the adapter source in the digest. A new server executable with unchanged banner data can therefore replace its older owned hooks after Warp closes, instead of keeping old closures for the whole Client process.

The release gate remains OFF. Still required before enabling:

  1. Current-process evidence for ItemHelper.GetItem(UInt32) and the actual returned ItemData; old MethodHandles and native addresses are invalid after a Client restart.
  2. One-banner card proof for node_content, text cloning/SafeSetTextID and the _async_load_sprite_to(image,path) call, including successful asynchronous art completion. A bytecode call site or ART_REQUESTED log alone does not prove the image appeared.
  3. Native response/refresh lifecycle proof: synthetic IDs can be skipped before original pool construction, and the custom registration must remain consistent when GetGachaInfo refreshes the module. Merely registering an initialized clone once is not evidence for every refresh.
  4. Draw 1/10 result/display behavior with the mapped avatar and configured duplicate materials, plus close/reopen behavior. No assertion of Client success should be made from compilation or native registration alone.

Current source has not been compiled or Client-exercised after this offline audit. All custom banner IDs remain the explicit resource-backed sandbox mapping; no additional game IDs, asset paths or group enum values were invented.