character_settings policy v2: the carry list is ten strict key patterns (core.ascension_active_spec, _starter, _starter_live, _reset_credits, _slot.active, _slot.#, _slot.#.build.#, _slot.#.bar.#, _build.#, _bar.#), matched exactly, not by prefix. Spell charges, Destiny Weaver, dynamic XP, Runemaster, prestige/glory, tutorial flags, Wildcard state and anything unknown are quarantined in the opaque extension and never applied. The importer's own marker source (coa.portable.) is dropped. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6.7 KiB
Portable export from a realm (Phase 2)
Code: crates/coa-core/src/portable/realm/. Scope decisions: PORTABLE_CHARACTERS_AUDIT.md sections 15-16.
Phase 2 is read-only. It reads one offline character out of a realm's characters database into a
PortableCharacter and (for "Make Portable") registers it in the local store. It never writes to a realm.
What it does
| Function | Meaning |
|---|---|
inspect_characters(db) |
"Inspect realm characters": every character of a non-bot account with the reasons it cannot be exported yet |
export_character(db, guid, character_id, prior_items) |
"Export snapshot": the portable model + item observations + warnings |
make_portable(db, store, profile, server_id, guid) |
"Make Portable": export, then register in the local store in one transaction (revision 1, bound to the realm, items mapped) |
No Tauri command or UI exists yet (Phase 9).
How the realm is read
- Through the Manager's existing
Dbabstraction (the bundledmysqlclient, or the same client inside a Docker database container). No driver, no new connection type, no published port. The Wildcard realm profile is routed byDbexactly like everything else, and the database decides the ruleset, not the caller. - Fixed SQL, typed input. Every statement is a literal in
script.rs. The only input is the character's local guid as au32formatted as a number. Table and column names of unknown tables come frominformation_schema, are checked as plain identifiers and appear only inSELECT COUNT(*). - One consistent read-only snapshot. All statements for a character run in one
mysqlsession insideSET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY;. Verified against a real server: a write inside the script fails, and a concurrent write by someone else in the middle of the script is not seen. - Hex-encoded text. Names, item texts, settings and macro blobs are returned as
x<hex>, so tabs, newlines, quotes and backslashes cannot confuse the parser. - Writing into a realm (Phase 3) must use prepared statements or the realm's own in-core import; this read path is not a basis for it.
Who may be exported (Blocker)
| Blocker | Rule |
|---|---|
Online |
characters.online <> 0 (also after a crash, when the flag is stale: stop the realm or log in/out) |
Deleted |
deleteDate is set |
BotAccount |
account COABOT* or COAMANAGER |
ActiveChallenge |
a row in coa_character_challenge |
ActiveGameMode |
coa_character_gamemode.gameMode <> 0 |
ActiveCustomTrial |
a row in coa_custom_trial_active |
PendingManastormCaches |
a row in ascension_manastorm_cache (it names item guids that would be orphaned) |
UnclassifiedState(table) |
a table this Manager does not know has a per-character column with rows for this character |
Finished challenges, game mode 0 and condition flags are history, not active state, and do not block (decision D6:
active state blocks). A realm that lacks a core table (SchemaMismatch) cannot be exported from.
What is exported (v1)
17 tables: characters, character_inventory, item_instance, character_gifts, character_spell,
character_talent, character_skills, character_glyphs, character_reputation, character_queststatus,
character_queststatus_rewarded (active = 1, as the realm loads it), character_action, character_pet, pet_spell,
character_pet_declinedname, character_settings (by policy), character_account_data (macros, type 5).
Not exported by decision (deferred): collections, the personal Ascension bank, equipment sets, per-character wardrobe
(character_appearance*), mod_craftsmans_codex. Auras, cooldowns, mail, guild, social, achievements, daily/weekly
quests, position and homebind are never part of v1.
Left out with a warning (the realm would delete them on load anyway): inventory rows without an item_instance,
items in a bag that is not on the character, items in buyback slots.
character_settings policy (policy.rs, version 2)
Only the minimal build / spec / starter / reset state is carried, and only by strict key patterns (# = 1-9 digits,
everything else literal; no broad prefixes):
core.ascension_active_spec core.ascension_starter core.ascension_starter_live
core.ascension_reset_credits core.ascension_slot.active core.ascension_slot.#
core.ascension_slot.#.build.# core.ascension_slot.#.bar.# core.ascension_build.# core.ascension_bar.#
- Quarantine: everything else (
core.spell_charge.*, Destiny Weaver, dynamic XP, Runemaster, prestige/glory, tutorial flags, Wildcard state, unknown sources) is kept in the snapshot as the opaque extensioncoa:unlisted-settingswith its values, and is never applied at a realm. - Drop:
coa.bot*,coa.gameplay_test,coa.highrisk,coa.portable.(the importer's own marker).
Version 1 (Phase 2) carried broad prefixes; version 2 is the corrective Phase 2.1 change.
Table registry (registry.rs)
All 163 tables of the characters schema are classified (Portable, Deferred, Collection, AccountLocal,
CharacterLocal, Blocking, Realm). A test fails if the real schema has a table the registry does not know, and at
export time an unknown table that holds this character's rows blocks the export. A silent half-export is not possible.
Item identity
Realms recycle local item guids. ItemObservation.identity hashes what cannot change during an item's life (entry,
random property, crafter). A re-export keeps an item's portable id only if the identity behind the local guid is
unchanged; a recycled guid produces a new portable item. The store side is in PORTABLE_FORMAT.md (item mapping
lifecycle).
Tests
realm/tests.rs: on recorded answers of a real MySQL (testdata/golden/), no database needed: naked level 1, geared level 80 (bags, enchants, gems, random suffix, crafted-by, texts, charges, gift, currency tokens), quests, reputation, action bars, 300 spells + talents + glyphs + skills + CoA settings + binary macros, hunter and summon pets with declined names, every blocker, item id stability and recycling, Wildcard ruleset, CRLF, 400 damaged answers (no panics), registration in the store (one transaction, refused twice, rolled back on failure).realm/live.rs(#[ignore]): the same answers re-recorded/verified against a real disposable MySQL, plus the read-only and snapshot-consistency proofs, the 21 genuine template characters of the schema fixture, and the proof thatmake_portablechanges nothing in the realm. Instructions are at the top of the file.tools/gen_portable_fixture.pywritestestdata/realm-fixture.sql(deterministic).