Files
GaCy-GO/docs/GACHA-NATIVE-ADAPTER.md
T

142 lines
13 KiB
Markdown
Raw Normal View History

2026-10-28 18:54:43 +07:00
# 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.