lity to force a level in Creature::SelectLevel()#2
Merged
Conversation
Cmangos commit bb91d5e
billy1arm
pushed a commit
that referenced
this pull request
Feb 15, 2017
…th the caster's group. Fixes #2.
billy1arm
added a commit
that referenced
this pull request
Jun 5, 2026
billy1arm
pushed a commit
that referenced
this pull request
Jun 18, 2026
* feat(livingworld): add per-cell loaded bitset to NGrid for B-Cell * feat(livingworld): add static_assert-tested cell-envelope math header * feat(livingworld): add LOG_FILTER_CELL_ENVELOPE debug filter * refactor(livingworld): make grid load cell-granular via idempotent LoadCell * feat(livingworld): add Map::EnsureCellEnvelopeLoaded (3x3 envelope load) * feat(livingworld): add LivingWorld.CellEnvelopeLoad config (default off) + version bump * feat(livingworld): load 3x3 envelope for player-less anchors via EnsureGridLoadedAtEnter * feat(livingworld): accrete envelope on same-grid anchor cell moves * feat(livingworld): add tier-2 anomaly capture + live .grid info state + counters * fix(livingworld): remove corpse re-add from envelope path; drop dead anomalyTouchUnloaded counter * feat(livingworld): add ObjectGridUnloader::MoveToRespawnCell for per-cell teardown * feat(livingworld): add Map::UnloadCell per-cell teardown primitive * feat(livingworld): add LivingWorld.CellEnvelopeDowngradeDelay config + version bump * feat(livingworld): track per-grid player count + downgrade timer * feat(livingworld): add Map::IsCellAnchorProtected envelope-coverage check * feat(livingworld): downgrade FULL anchor grids back to ENVELOPE after players leave * feat(livingworld): trailing unload as moving anchors advance (sliding envelope) * feat(livingworld): show B-Cell unload-side counters in .grid lwstats * fix(livingworld): downgrade timer must accrue real time; cheap grid-reject in IsCellAnchorProtected Review fixes on PR2: - GridStates ActiveState::Update: the FULL->ENVELOPE downgrade timer was Update()'d with one tick's t_diff but only inside the periodic info-timer gate, so it accrued ~grid_expiry/10 too slowly and the downgrade fired hours late. Move it to the outer per-tick scope (cheap FULL + no-player gate; ActiveObjectsInGrid()+downgrade only on elapse) so it honours CellEnvelopeDowngradeDelay. - Map::IsCellAnchorProtected: add a grid-distance early-reject (a 3x3 envelope reaches at most one grid away) before the per-cell test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): don't downgrade a grid a neighbouring player can see (thrash fix) A player's visibility FULL-loads adjacent grids, but the FULL->ENVELOPE downgrade gated only on the grid's own in-grid playerCount. A player standing in grid 14,32 thus FULL-loaded neighbour 14,31 (playerCount 0 there), the downgrade fired, the player's visibility instantly reloaded it -> 86 downgrades at ~2s, ignoring the 30s delay, NPCs popping for the player. Add Map::HasPlayerInOrAroundGrid (3x3 grid-block player check) and gate the downgrade on it: reset the timer while any player is in or adjacent, only accrue toward the delay once the whole 3x3 is player-free. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(livingworld): add cell-loaded to .npc watch (per-cell envelope state) grid-loaded reports the whole-grid FULL flag, which reads 'no' for an ENVELOPE grid even when the watched NPC's own cell is resident. Add Map::IsCellLoaded (read-only per-cell bitset query) and surface cell-loaded= at both .npc watch and .npc watch last print sites, so an anchor in an envelope grid shows grid-loaded=no cell-loaded=yes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): tick resident envelope cells in Map::Visit without forcing FULL load * fix(livingworld): count/log accretion only on actual cell loads, not every crossing The accretion counter + trace fired on every anchor cell-boundary crossing, so a world of patrolling anchors produced ~16k 'accretion' log lines / 16 min while only ~5k cells were truly loaded -- flooding the log and making .grid lwstats 'accretions' read 'steps taken' rather than 'cells accreted'. Gate both on EnsureCellEnvelopeLoaded returning true (it already logs each loaded cell), so the metric is meaningful and the log is quiet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): restore upstream grid-load re-entrancy invariant Decouple the grid-bool guard from the cell-bitset: add markGridObjectDataLoading() to NGrid so the FULL flag is set before LoadN (matching upstream re-entrancy guard) without prematurely setting all 256 cell bits. setGridObjectDataLoaded(true) remains after LoadN to finalise the full bitset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): Map::Remove must not skip ENVELOPE-resident objects Gate removal on the object's cell residency (isCellObjectDataLoaded) instead of the grid FULL flag, so objects in envelope cells are fully removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): cell-aware respawn relocation in UnloadCell Add i_cellGranular to ObjectGridRespawnMover. When true, relocate creatures whose current cell differs from their respawn cell (not only DiffGrid). MoveToRespawnCell uses cellGranular=true; MoveToRespawnN keeps grid semantics. Prevents same-grid wanderers from being deleted during per-cell teardown. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): ForceLoadGrid must FULL-load, never envelope-load Replace EnsureGridLoadedAtEnter with direct EnsureGridLoaded + active-state setup so ForceLoadGrid always yields a FULL grid with explicit unload lock, identical to flag-off behaviour. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): don't bounce non-active creatures off ENVELOPE-resident destination cells Gate cross-grid rejection on destination cell residency, not the FULL flag. A non-active creature may path into an adjacent envelope-resident cell; only genuinely-unloaded destinations still send it to respawn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): make pool/direct spawn checks cell-aware Replace map->IsLoaded with map->IsCellLoaded at spawn sites so a pool or direct spawn into an envelope-resident cell is instantiated, not deferred. Files: PoolManager.cpp, Creature.cpp, GameObject.cpp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): defer cell teardown out of the update/iteration path Queue pending cell unloads from trailing-unload and DowngradeGridToEnvelope, then drain them at the end of Map::Update after all iterations complete. Re-checks residency and anchor protection at drain time. Prevents iterator invalidation from object deletion mid-iteration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): verify corpse safety on cell teardown Document that UnloadCell only visits GridTypeMapContainer; resurrectable player corpses are world objects (WorldTypeMapContainer) and are already preserved during cell teardown. No behavioural change required. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): scope envelope loading to continent world maps Add IsContinent() guard so only continent world maps use the envelope path; instances and battlegrounds fall through to normal FULL load. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): promote ENVELOPE neighbour grids to FULL for nearby players When a player is added or changes grid, iterate the 8 neighbour grids and FULL-load any that are resident but ENVELOPE. Restores visual parity with the pre-B-Cell baseline at grid boundaries without forcing empty neighbours. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): document FULL-grid bitset semantics Clarify that isCellObjectDataLoaded means 'cell is covered/scanned' (FULL grid or envelope-loaded), not 'cell contains DB objects'. No behavioural change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): Allman braces in GridCommands.cpp lwState assignment Expand compact single-line if assignments to Allman-braced multiline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(livingworld): pin SD3 submodule at master-reachable 10e471f (defer scan-guard bump) Commits a6ae561 (re-entrancy fix) and 9f27f65 (bitset doc) inadvertently staged the SD3 gitlink to the unmerged scan-guard branch tip (54d2025) as a side effect of `git add`. A server PR must reference a commit reachable from ScriptDev3 master, so reset the recorded pin back to 10e471f. The scan-guard + anomaly-key changes live on branch livingworld/bcell-sd3 (tip 54d2025) and land via their own PR into mangos/ScriptDev3; the server submodule pin is bumped to the merged SHA as the final landing step. The working tree stays checked out at 54d2025 so local builds keep the scan-guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): harden deferred cell-unload against freed grids; tighten neighbour promotion PR2.1 review follow-up (issues found while reviewing the fix stack). 1. Deferred cell-unload use-after-free (Task 7): m_pendingCellUnloads stored raw NGridType* pointers. Within one Map::Update a cell can be enqueued during the active-object loop, then its grid fully unloaded by UnloadGrid() in the grid-state loop -- which runs before the drain and does `delete getNGrid(...)` -- leaving a dangling pointer that ProcessPendingCellUnloads() then dereferences. Store grid coords instead and re-resolve via getNGrid() at drain time (null -> skip). 2. Neighbour promotion over-broad (Task 10): PromoteEnvelopeNeighboursToFull() promoted any non-FULL existing grid, including a created-but-empty one. Require loadedCellCount() > 0 so only genuine ENVELOPE grids are promoted. Builds clean (RelWithDebInfo, exit 0, no new warnings). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): reject non-active creature relocation into a non-resident cell PR2.1 review follow-up (Codex 2nd-pass High #1). CreatureCellRelocation only validated/loaded the destination cell on cross-grid moves. A same-grid relocation went straight to RemoveFromGrid/AddToGrid without checking residency. The cell-aware respawn relocation (UnloadCell -> MoveToRespawnCell) can send a wandered creature home into a same-grid cell that was already unloaded earlier in the same teardown drain; the creature then sits in a non-resident cell, which Map::Visit skips (frozen) and Map::Remove skips (unremovable). After the cross-grid load attempt, reject any non-active creature whose destination cell is still not resident (same-grid or cross-grid). The caller (CreatureRespawnRelocation -> Unload) then deletes + reschedules it via SaveRespawnTime instead of stranding it. Active anchors are exempt: their accretion path loads the destination envelope before AddToGrid. Builds clean (RelWithDebInfo, exit 0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): promote an ENVELOPE grid to FULL when it appears next to a stationary player PR2.1 review follow-up (Codex 2nd-pass High #2). PromoteEnvelopeNeighboursToFull only fires on player add/relocation, so an ENVELOPE grid created or extended NEXT TO a stationary player -- an anchor wandering in (accretion) or being registered active nearby (AddToActive) -- was not promoted, re-introducing the boundary visibility pop-in for that case. Add MaybePromoteEnvelopeGridForPlayer(gridX, gridY): if the grid is a genuine ENVELOPE (loadedCellCount > 0, not FULL) and HasPlayerInOrAroundGrid is true, promote it to FULL. Call it from the accretion path in CreatureCellRelocation and from AddToActive. Gated on CellEnvelopeLoad; flag-off is unchanged. Composes with the existing downgrade guard (HasPlayerInOrAroundGrid blocks teardown while a player is near), so no thrash. Builds clean (RelWithDebInfo, exit 0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(livingworld): require lwstats DB structure update * chore(livingworld): use standard header for cell envelope * fix(livingworld): mark respawn mover constructor explicit --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
MadMaxMangos
added a commit
that referenced
this pull request
Jul 1, 2026
… externalized AH reads (coordinator) (#414) * feat(mangosd): in-process -t test-mode scaffold (custody groundwork) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mangosd): _exit after -t test to skip Eluna teardown crash (reliable exit code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(db): add CommitTransactionChecked synchronous durable commit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(mail): SendMailToInTransaction co-commit variant + ordered deferred queue Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custody): CustodyDeferred online-item rollback lifetime + self-contained header Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(db): harden CommitTransactionChecked (begin-failure, stopped-thread, exception safety) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * build(character): require Character DB 22_05_001 for custody_ledger Bumps the required Character DB version to match the Rel22_05_001 custody_ledger migration (mangoszero/database). Separated from the CommitTransactionChecked infra commit so the shared-DB change carries no feature/migration coupling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(custody): custody_ledger persistent model + CRUD selftest Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(custody): CustodyService primitives + ordered deferred context Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * build(custody): add AH.Service.Custody + CustodyCrashAt + bump MANGOS_WORLD_VER Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(custody): route S1 auction-create through the custody ledger (gated) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(custody): route S2/S3 bid+buyout through the custody ledger (gated) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custody): route custody buyouts to legacy path (avoid nested-txn abort until Task 10) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custody): S2 restore-on-failure + fail-closed bid lookup + scalar notify closure (Codex NO-SHIP) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mail): re-resolve deferred mail-push receiver by GUID (I2 scalar-only closure) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custody): S1 restore-on-failure (refund deposit, drop phantom auction, return item) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custody): scalar-only command-result closure + active-rows gate + auction-id reuse guard (Codex re-review) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custody): NextBidSeq uses MAX(id) not COUNT to avoid key collision after TTL prune (GLM review) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(custody): route S4 win/resolve + buyout through the custody ledger (gated) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custody): S4 deferred closures scalar-only (drop AuctionEntry* capture) + remove dead GetLiveBidKey Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custody): mail items survive rollback (dispose only on success) - fixes win-resolution use-after-free (GLM) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(custody): route S5 auction-cancel through the custody ledger (gated) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah-custody): terminalize item ledger row on the custody win path (S4) AuctionBidWinningCustody flipped the dep: and bid rows but never the item:<Id> row; SendAuctionWonMailInTransaction moves the item (owner UPDATE or item_instance DELETE) without a ledger flip. So every custody sale/buyout/bot-win left the item:<Id> row CST_RESERVED after the auction row was deleted -> an orphaned non-terminal row, which breaks the reconciliation invariant ("every non-terminal row maps to a live auction") that Tasks 13/14 depend on. Add one in-txn CommitGoldLedgerOnly("item:"+Id) immediately after SendAuctionWonMailInTransaction and before DeleteFromDB. On a win the item always resolves (delivered or destroyed), so the flip is unconditional and safe; it is in-txn so on rollback it rolls back with everything else (no orphan, no false terminal). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ah-custody): route unsold-expiry through checked custody txn (S6) The no-bidder expiry branch of AuctionHouseObject::Update() now routes custody-row auctions through a single checked co-committed transaction (per-auction HasRows gate, X3), mirroring the win-branch gate already at :890-908. S6 is the simplest seam: no bid, no winner, no cut, and NO synchronous in-memory mutation -> no X6 restore (every effect is deferred and does not run on rollback; the auction + item survive intact and the next tick re-resolves cleanly). AuctionHouseMgr.cpp: - SendAuctionExpiredMailInTransaction: co-commit mirror of SendAuctionExpiredMail. Owner-exists branch: defers the online owner's SMSG_AUCTION_OWNER_NOTIFICATION (expired form, sold=false, bid/outbid/bidder all 0) BEFORE the mail push (notify-then-mail, legacy :307), defers RemoveAItem FIRST then zeroes itemGuidLow, and returns the item via CustodyService::DeliverItem (flips "item:<Id>" -> TERMINAL_OK AND co-commits the return mail). Destroy branch: in-txn item_instance DELETE + CommitGoldLedgerOnly("item:"+Id) (the §0 lesson -- no mail means DeliverItem is not called, so the escrow row must be terminalized explicitly), defers RemoveAItem + live delete pItem (X5: destroy only on commit success). - AuctionEntry::ExpireUnsoldCustody: orchestrator mirroring AuctionBidWinningCustody. Returns the item (or destroys it), forfeits the deposit to the house (CommitGoldLedgerOnly("dep:"+Id), house sink, no money/mail), deletes the auction row IN-TXN, and defers the AH-map erase + object delete LAST so `this` stays valid throughout def.run(). - Update() no-bid branch: gated custody path (symmetric with the win branch); legacy path unchanged behind the else. AuctionHouseMgr.h: declare both new methods beside their win-path siblings. Gate-OFF (no custody rows) is byte-identical to legacy. Eluna: legacy expiry fires no AH hook, so S6 fires none either (parity audit in the follow-up Eluna batch). Build green; -t custody green. The SQL differential (online/offline/deleted-account seller) needs the human's quiesced realm (Task 15). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah-custody): fire Eluna AH hooks success-only in legacy order across custody seams Audit every custody seam for Eluna AH-hook parity (success-only + legacy sequence position + live entry): S5 Cancel (AuctionHouseHandler.cpp): OnRemove was fired inside if (CommitTransactionChecked()) -- success-only, good -- but BEFORE def.run(), i.e. not at its legacy sequence position among the cancel effects. Move it into a def.effects closure appended AFTER the refund/item-return effects and BEFORE the trailing RemoveAuction/delete closure, so it runs in legacy order during def.run() and still sees a live entry. Captures auctionHouse + auction by value (both live until the final delete effect runs later in the same def.run() pass). Wrapped in #ifdef ENABLE_ELUNA as before. S1 Create: CONFIRMED already correct -- the X6 rollback path returns at :641 before the OnAdd call at :658, so OnAdd fires only on a successful checked commit. No change. S4 Win: legacy AuctionBidWinning (AuctionHouseMgr.cpp:1274-1291) fires NO Eluna AH hook, so the custody win path (AuctionBidWinningCustody) fires none either. No change. S6 Expire: legacy expiry (SendAuctionExpiredMail + the no-bid Update() branch) fires NO Eluna AH hook, so ExpireUnsoldCustody fires none either. No change. Build green; -t custody green. Eluna behavior itself is validated only with an Eluna build + a Lua hook script (manual follow-up); noted here as the brief's stated test caveat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah-custody): keep S6 expire rollback-safe (drop synchronous itemGuidLow=0) Adversarial-review fix. SendAuctionExpiredMailInTransaction zeroed auction->itemGuidLow synchronously (before the checked commit) in BOTH the owner-exists and destroy branches. That is an in-memory mutation that SURVIVES a CommitTransactionChecked() rollback -- contradicting the seam's own invariant ("S6 makes NO synchronous in-memory mutation; on rollback the auction + item survive intact and the next tick re-resolves cleanly"). On rollback: def.run() does not fire (the auction stays in the map) but itemGuidLow is now 0; the custody rows revert so HasRows() stays true. The next Update() tick re-enters the seam, GetAItem(0) returns NULL -> early "item not found, and lost" return, yet ExpireUnsoldCustody continues to forfeit the deposit + DeleteFromDB + delete the auction. Net: item never returned to the player, "item:" row orphaned CST_RESERVED, live Item* leaked. Fix: remove both synchronous `auction->itemGuidLow = 0;` writes. Nothing reads itemGuidLow after that point on the success path (the deferred RemoveAItem uses the savedItemGuidLow snapshot; the auction is deleted by the trailing deferred closure), so the field never needs zeroing; on rollback it must stay valid for the next-tick re-resolution. This matches S4's SendAuctionWonMailInTransaction, which never zeroes it either. Also correct the now-stale savedItemGuidLow snapshot comment in both senders. Build green; -t custody green (custody OK / EXIT=0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah-custody): fire S5 Eluna OnRemove in exact legacy order (RemoveAuction -> OnRemove -> delete) Adversarial-review refinement (Eluna parity, Minor). The moved S5 OnRemove hook fired BEFORE the trailing RemoveAuction closure, i.e. while the auction was still in the AH map -- whereas the legacy non-custody branch fires OnRemove AFTER RemoveAuction (out of map) and before delete. A Lua OnRemove handler that queried the auction house during the callback would see the auction present on the custody path but absent on legacy. Fold OnRemove into the trailing deferred closure between RemoveAuction and delete self, reproducing the legacy order exactly (RemoveAuction out-of-map -> OnRemove -> delete). Still success-only (the closure runs only in def.run() after a successful checked commit); `self`/auctionHouse stay valid until the delete at the end of the closure. Build green; -t custody green (custody OK / EXIT=0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah-custody): terminalize item row on the S6 missing-item path + correct stale itemGuidLow contract docs Independent-review (GLM/Codex) NO-SHIP fixes. IMPORTANT: SendAuctionExpiredMailInTransaction returns early when GetAItem(itemGuidLow) is NULL (item already absent from the live AH cache), WITHOUT terminalizing the "item:<Id>" escrow row -- but ExpireUnsoldCustody still forfeits the deposit and deletes the auction row unconditionally. A custody auction could thus be removed while item:<Id> stays CST_RESERVED, orphaning a non-terminal custody row with no live auction (breaks the reconciliation invariant Tasks 13/14 depend on). Requires a degraded state (cache drift / partial prior corruption) to trigger, but the seam should not introduce a ledger orphan. Fix: flip "item:<Id>" -> TERMINAL_OK ledger-only in the early-return branch (legacy player-visible behavior unchanged -- the item is already lost; item_instance is intentionally NOT touched, matching legacy, since its DB state is unknown when the cache has drifted). MINOR: the function doc comment (AuctionHouseMgr.cpp) and the header declaration comment (AuctionHouseMgr.h) still claimed "itemGuidLow=0 ... are deferred", stale after 4ced3e7 removed the synchronous zero entirely. Both now state that itemGuidLow is intentionally NOT zeroed on the custody path (success deletes the AuctionEntry; rollback needs the original GUID for the next-tick re-resolution), so a future maintainer does not reintroduce either a synchronous or a deferred zero. Build green; -t custody green (custody OK / EXIT=0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ah-custody): add reconcile sweep and terminal prune Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ah-custody): add console-only 'ah repair' custody-drift command Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah-custody): make repair terminalize drift without minting value Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ah-custody): wire crash-injection hooks (pre-commit/pre-deferred) into the custody seams Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(ah-custody): add custody differential SQL, crash-injection procedure, operator docs (Task 15) - src/ah-service/tools/custody_diff.sql: the spec Section 8 player-visible projection diff (characters/mail/mail_items/item_instance/auction), count-aware + NULL-safe, parameterized clone schemas, custody_ledger excluded, mail-time normalization toggle. - src/ah-service/tools/custody_crash_test.md: the crash-injection procedure against the now-wired pre-commit/pre-deferred hooks, the forced-rollback (CommitTransactionChecked()==false) case, the full Section 8 matrix, and the promotion gate. - doc/AuctionHouseBot.md: custody operator section (AH.Service.Custody + AH.Service.CustodyCrashAt keys, per-auction HasRows gate, the console-only 'ah repair' terminalize-without-disbursing + force-forfeit escape hatch, promotion checklist). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah-custody): address GLM Task-15 review (auction-time normalization + crash-log flush + doc clarifications) I1 (Important): custody_diff.sql had no normalization toggle for auction.time (= expireTime, a wall-clock timestamp set at creation), so an unpinned-clock A/B run would false-positive every created auction in AUCTION_DIFF with no escape. Add @compare_absolute_auction_times (default 0) mirroring the mail toggle: the auction temp tables now NULL out `time` by default, AND the AUCTION_DIFF join compares `time` with the NULL-safe <=> (a plain `=` makes NULL=NULL fail and would report every row -- the gap in the review's suggested minimal fix). Precondition note updated. M1 (Minor): MaybeCrash flushed only stdout before _exit(3); the sLog crash line (written to the log FILE*) could be lost. Use fflush(NULL) to flush all stdio streams so the crash-test log assertion is reliable. M2 (Minor): custody_crash_test.md pre-commit recovery assertion said "the auction is intact", misleading for S1 (create) where no row was ever inserted. Reworded to "pre-seam DB state unchanged -- for S1 no auction row exists; for S2-S6 the existing row is unchanged". M3 (Minor): custody_diff.sql notes that a clean MAIL_DIFF with a dirty MAIL_ITEMS_DIFF means mail.id drifted between clones (re-verify the idle-snapshot precondition). Build green; -t custody green (custody OK / EXIT=0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(ah-custody): add in-game promotion-gate runbook Ties the Section 8 promotion gate together for an operator: prerequisites (build c08a6b7, the two pending mangos0 World migrations, the character0 A/B clones, the custody conf keys), the seeded quiesced fixture, the A/B differential procedure (single-server two-pass + custody_diff.sql invocation + packet-order check), per-seam crash-injection, concurrent-observer, an ah repair drift smoke, and the final promotion checklist + instant rollback note. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahbot): forged system owner -- name intercept + constants + default Create AhBotSystemOwner.h (AHBOT_SYSTEM_OWNER_NAME/GUID + helpers), intercept GetPlayerGuidByName so "AuctionHouse" (case-insensitive) resolves to ObjectGuid(HIGHGUID_PLAYER, 0xFFFFFFFE) without a DB round-trip, default AuctionHouseBot.CharacterName to AHBOT_SYSTEM_OWNER_NAME, and add the -t ahowner self-test (TDD: RED before intercept, GREEN after). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahbot): exclude the forged system GUID from the player allocator Wire SkipAhBotSystemOwnerGuid into SetHighestGuids so the char-GUID allocator's next value can never land on the reserved 0xFFFFFFFE sentinel; add belt-and-suspenders contract assertions to -t ahowner. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahbot): self-heal the reserved system name on every load LoadReservedPlayersNames() now seeds AHBOT_SYSTEM_OWNER_NAME into m_ReservedNames in-memory on every call (both the early-return/empty-table branch and the main path after the DB loop), so IsReservedName(AuctionHouse) returns true even if the reserved_name table has been wiped. Assertion added to the -t ahowner test verifies this. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahbot): reject player mail to the forged system owner Gate added in HandleSendMail after the receiver GUID resolves: if IsAhBotSystemOwnerGuid(rc) is true, send MAIL_ERR_RECIPIENT_NOT_FOUND and return. The bot's own auction proceeds/returns use the internal MailDraft::SendMailTo path which bypasses this handler, so the bot still receives them. Kills COD scams and accidental gifts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahbot): don't loop-spawn the worker on an unresolved bot GUID + operator doc Guard at the top of WorkerSupervisor::Start() returns false immediately when m_botGuid == 0 (AuctionHouseBot.CharacterName unresolved), preventing the supervisor restart loop from running with a useless GUID. The forged system owner always resolves to a non-zero sentinel; 0 means misconfiguration. Adds -t ahowner assertion that Start() refuses with botGuid=0, and adds operator-doc note to doc/AuctionHouseBot.md explaining the reserved system character. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ahworker): relocate ah-service to src/modules/AhWorker (no behavior change) Move the out-of-process worker under the SD3/Eluna module convention so it is submodule-ready. modules/ is added when BUILD_MANGOSD OR BUILD_AH_SERVICE; SD3 and Eluna self-gate on BUILD_MANGOSD AND SCRIPT_LIB_* (they link `game`), while AhWorker self-gates on BUILD_AH_SERVICE and links only ah_ipc + shared. Binary name, install path, and conf template are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ipc): browse wire contract (length-prefixed, deferEluna, strict decode) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ipc): validate inbound frame size on the worker client handler (I8) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahworker): item_instance.data blob decoder for enchant/suffix/charges (I9) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahworker): pure FilterAndPaginate (name filter, page slice, deferEluna set) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahworker): full-parity CanUseItem port as ah_usability lib (rep/honor/proficiency/mount/recipe-set) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(game): AhUsabilityRef evaluator (AhUseResult, D1/D2) + CanUseItem delegates + CanUseItemEluna (D5) Caller audit: CanUseItem(proto) callers that forward specific EQUIP_ERR codes all go through CanUseItem(Item*) which delegates here — codes preserved 1:1 by MapAhUseResult (exhaustive switch + static_assert AHUSE_OK==EQUIP_ERR_OK). Direct proto-overload callers (ViableEquipSlots, NeedBeforeGreed, playerbot, LookupCommands, AuctionHouseMgr) only check == EQUIP_ERR_OK; unaffected. TDD: ahusabilityref RED (LNK2019 before cmake reconfigure), GREEN after; ahowner GREEN (regression). Both exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ahworker): separate ah_usability_difftest exe (worker port vs production ref) (C5/I10) Adds an EXCLUDE_FROM_ALL differential drift-test executable that runs AhUsability::IsUsable (worker) and AhUsabilityRef::Evaluate (reference) over a 15-item x 3-profile battery and asserts 0 drift. AhUsabilityRef.cpp is compiled directly (not via linking game) to surface any boundary violation at build time. Fix: ACE_DOESNT_DEFINE_MAIN must be defined before any include on Windows to prevent ACE's OS_main.h from hijacking `main`. Run: 45/45 agree, 0 drift, exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ah): extract BuildListForKind in-process browse helper (fallback core, C1/I1) Adds AuctionHouseObject::BuildListForKind, a thin dispatcher that routes browse requests to the existing BuildListAuctionItems/BuildListOwnerItems/ BuildListBidderItems (kind 0/1/2) so Task-11 timeout fallback has a single re-entrant call site. For BIDDER the client outbid ids are prepended in CLIENT ORDER before the sweep, matching HandleAuctionListBidderItems exactly. The three inner builders are completely unchanged (behavior-preserving). Also adds AhAppendClientOutbidsForTest free helper (test seam) and -t ahbrowsehelper smoke test locking the order-preserving contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahworker): on-demand Fetch (full parity + blob decode + I3 status) + per-thread browse thread + defer - BrowseHandler::Fetch: cross-DB JOIN (auction x item_instance x world.item_template), AhItemBlob decode, locale overlay (single LEFT JOIN, LocaleConstant suffix V3/D7), COUNT(*) probe to distinguish FETCH_EMPTY vs FETCH_DB_ERROR (I3), D6 proficiency from GetItemProficiencySkill, outbid/curBid/timeLeftMs at full parity. - BrowseThread (ACE_Based::Runnable): explicit BoundedQueue<BrowseQuery>(256,8MB), per-thread MySQL ThreadStart/ThreadEnd (C4), atomic stop, D4 tooMany reply on queue-full via SendFrame (confirmed thread-safe), FETCH_DB_ERROR sends no reply (mangosd TTL fallback), I8 outbound size preflight. - Main.cpp: browse dispatch selftest (RED->GREEN), BrowseThread start after handshake, IPC_BROWSE_QUERY case, guaranteed join before DB/client shutdown (C4). - --selftest: exit 0 confirmed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ahworker): make BrowseThread diagnostic counters atomic (review) m_processed/m_rejected/m_dbErrors were plain uint64 but written from two threads (Submit on IPC-dispatch; run() on browse thread) — latent data race and torn 64-bit read on non-x86. Changed to std::atomic<uint64> with fetch_add(relaxed) at increment sites and load(relaxed) in getters, matching the existing m_stop precedent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ah): async browse proxy + pending-map (cap+GUID re-resolve+seq) + bounded Eluna pass + fallback + sub-budget Task 11: mangosd's three AH list handlers become async proxies to the ah-service worker. BrowsePendingMap (MAX_PENDING cap=D3; per-(char,kind) seq + playerGuidLow re-resolve=I4; one GetGameTime clock=M5). World IPC_BROWSE_RESULT branch: tooMany in-process fallback FIRST, then the deferred-Eluna pass (CanUseItemEluna over the full surviving set, no per-tick budget=D5) + re-pagination; TTL sweep. C1 send-fail /timeout in-process fallback via BuildListForKind. AhEnsureRecipeCastMap built at the TOP of LoadAuctionItems before its empty-AH early return (V6/D9). V3 locale LocaleConstant on the wire. I1 bidder client-outbid-first. I5 cross-faction. I6 WS_DRAIN_BROWSE_PER_CALL sub-budget. -t ahbrowsepending + regressions green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(ah): add MaNGOS GPL header to new SP-1 source files ItemInstanceFields, Usability, AhUsabilityRef, and the difftest were created without the standard license header (the briefs only mandated it for BrowseMessages.h). Prepend the verbatim 20-line MaNGOS GPL block; comment-only, BOM-free, no behavior change. ah-service + ah_usability_difftest build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah): Task 11 review -- Allman braces, totalcount assert, BIDDER eluna guard, M5 comment, sweep test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(ah): SP-1 conf floor (2 char DB conns), operator doc, full smoke procedure Raise CharacterDatabaseConnections default 1->2 with reworded M4 comment (pool floor, not a dedicated browse-thread connection). Extend doc/AuctionHouseBot.md with the SP-1 read-model operator note (worker-up forwarding, deferred-Eluna, fallback contract, proficiency parity). Smoke procedure written to Memory tree (untracked). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ahworker): coordinator flip pt1 -- prePaginated wire flag + over-cap pagination SP-1 coordinator flip (worker-mandatory reads). The worker's deferred-Eluna path no longer signals an in-process fallback on overflow; instead it pre-paginates server-side so mangosd never has to serve the browse itself. - BrowseResult gains uint8 prePaginated. tooMany is repurposed to mean ONLY "worker could not serve (queue full / oversize failsafe)" -> mangosd will send "AH unavailable"; prePaginated means ">cap deferred-Eluna: entries ARE the listfrom page, run the Lua veto on the page, do not re-paginate". The two are mutually exclusive by construction. (IPC_BROWSE_RESULT is a MAXLEN rule, so the +1 byte needs no rule change; Decode min-size guard +1.) - FilterAndPaginate collects the listfrom..+50 survivor window alongside the un-paginated set; >cap -> prePaginated=1 + ship just that page (was tooMany=1 + clear entries). The common <=cap path is unchanged (exact in-process parity: mangosd runs Eluna over the full set, then paginates). - D4 (queue saturated) and I8 (oversize failsafe) keep tooMany=1 (now unambiguously "unavailable"); comments updated off "in-process fallback". - selftest: codec round-trip carries prePaginated; the two strict-decode buffers gain the byte; new >cap test asserts prePaginated=1, 50-entry page at listfrom, totalcount=1200. Build clean; ah-service --selftest exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ah-sp1): coordinator flip pt2 -- mangosd serves "AH unavailable", no in-process read fallback mangosd becomes a pure coordinator for the AH read paths. When the worker is the authority and cannot serve, the player gets "AH temporarily unavailable" instead of an in-process browse. Mutations stay in-process in SP-1 (unaffected). - AuctionHouseHandler.cpp: delete AhServeBrowseInProcess; add AhSendBrowseUnavailable (empty SMSG_AUCTION_*_LIST_RESULT so the client does not hang + a red system line). The 3 read handlers: every worker-active transient failure (register cap / encode oversize / SendFrame fail) now sends unavailable; a NEW `if (sv)` branch sends unavailable when a worker is configured but DOWN. sv==NULL (no worker configured = legacy single-process) still falls through to the in-process path. AhResolveHouse is KEPT (the BIDDER outbid-prepend uses it -- it is not a fallback helper). - World.cpp: forward-decl AhSendBrowseUnavailable (drop AhServeBrowseInProcess); TTL sweep + reply-branch tooMany -> unavailable; the deferred-Eluna pass now splits on prePaginated -- common case runs Eluna over the full set then paginates (exact parity), the >cap edge runs Eluna on the worker's pre- paginated page only and does not re-paginate (decision #2). - BuildListForKind + its -t ahbrowsehelper test are kept (deleted wholesale at SP-6). Build clean; -t ahbrowsepending/ahbrowsehelper/ahusabilityref all OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(ah-sp1): operator doc for the coordinator flip (worker-mandatory reads) The SP-1 section described the removed in-process read fallback. Replace it with the coordinator model: a configured-but-unavailable worker yields "AH temporarily unavailable" (empty list + red chat), nothing served in-process; a worker fault degrades only the AH, never the realm; no-worker builds keep the legacy in-process AH. Add a smoke note for the accepted decision-#2 cosmetic edge (Eluna realm + >1000 usable hits -> a few extra page buttons / sparse deep pages) so it is not mis-flagged during testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ah-sp1): AH unavailable -> center-screen flash + gate the window-open when worker down Player-facing polish for the coordinator flip: - AhSendUnavailableMessage: one shared "AH temporarily unavailable" responder -- a transient center-screen SMSG_NOTIFICATION flash PLUS the persistent red system chat line. AhSendBrowseUnavailable now routes its message through it (browse/owner/bidder empty-list replies + the World.cpp reply/sweep paths). - SendAuctionHello gate: when a worker is the AH authority and it is down, do NOT open the auction window at all -- send the unavailable message and return. This is the single chokepoint for every open path (auctioneer click, gossip option, .auction GM commands), so the AH stays fully dark while the worker is down. (sv == NULL = no worker configured = legacy single-process -> open normally.) An already-open window cannot be force-closed in 1.12 (no opcode); a window opened while the worker was up then sees the browse-unavailable reply. Build clean; -t ahbrowsepending/ahbrowsehelper/ahusabilityref all OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(ah-sp1): document the center-flash + window-open gate in the unavailable behavior Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ahworker): empty filtered browse must not false-positive as cross-DB failure The FETCH_DB_ERROR detection compared a FILTERED query's legitimate 0-row result against an UNFILTERED `SELECT COUNT(*) FROM auction`, so any empty filtered browse -- an OWNER list with no auctions, a BIDDER list with no bids (e.g. right after a buyout deletes the won auction), a LIST whose item filters match nothing, or an empty house -- was misread as a broken cross-DB JOIN. Pre-flip that fell back to in-process (masking it); post-flip it surfaced as a 10s TTL hang then "AH unavailable" for legitimately-empty browses. Fix: when the filtered query returns 0 rows, probe the BARE cross-DB JOIN (no WHERE filters). If it returns a row, the cross-DB wiring is fine and the query simply matched nothing -> FETCH_EMPTY (serve an empty list immediately, no TTL hang). Only if the bare JOIN is also empty while `auction` is non-empty is it a genuine config failure -> FETCH_DB_ERROR. Verified live: bare JOIN returns 2714 (= full auction count) while buyguid/itemowner filters legitimately return 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(ah-custody): point promotion runbook at renumbered AH Repair migration The World AH Repair migration was renumbered Rel22_05_003 -> _005 in the database repo (collision with the merged #152 ItemAndSkinGroupFix). Update the promotion runbook to apply the full chained World sequence (_002.._005) so _005's version gate is satisfied, and confirm the DB tip is 22/5/5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(ah): move custody_diff.sql to the database repo; repoint docs The AH custody differential is SQL and does not belong in the server repo. Relocated to the mangoszero/database repo at Tools/custody_diff.sql (alongside the other diagnostic SQL there). Repoint the three references (AuctionHouseBot.md, custody_crash_test.md, custody_promotion_runbook.md) to the new location, and fix a sibling stale path (custody_crash_test.md still pointed at the pre-relocation src/ah-service/tools/ path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ah): remediate Codex review of the coordinator flip (F1-F5, F8-doc) Adversarially-verified findings from the SP-1 coordinator-flip code review: - F1 (critical): a configured-but-failed worker Start() nulls the supervisor pointer, and the read handlers keyed unavailable-vs-legacy off that pointer, so a configured worker that failed to start silently reverted to in-process reads and opened the window -- defeating the coordinator invariant. Add a persistent World::IsAhServiceConfigured() authority flag (set before Start(), survives the failed-start teardown) and re-key all four chokepoints (SendAuctionHello + browse/owner/bidder down-branches) off it. - F2 (critical): over-cap deferred-Eluna shipped a worker-paginated page that mangosd veto-filtered on the page only -> short page + pre-veto totalcount. Per the coordinator's correct-or-unavailable contract, the worker now declines the over-cap case (tooMany -> "AH unavailable"). prePaginated is retired entirely: wire field, encode/decode, the mangosd consumer branch, the page-window collection, and both worker selftests. - F4: BrowseResult::Decode now fails closed on semantically-invalid flag combinations (booleans 0/1; tooMany mutually exclusive with elunaPending and carries no entries). - F5: an invalid-UTF-8 search name now sends a terminal SMSG on both browse paths (proxy -> unavailable, legacy -> empty list) instead of a bare return that left the client's browse panel hung. - F3: the cross-DB health probe now mirrors the real query's locales_item join and localized column, so a locale-schema fault reports unavailable instead of slipping through as a false empty result. - F8 (documented, per maintainer decision): Eluna Player:SendAuctionMenu builds MSG_AUCTION_HELLO directly and bypasses the window gate -> recorded as a known limitation in doc/AuctionHouseBot.md rather than patching the third-party Eluna module. Builds clean (RelWithDebInfo); ah-service --selftest green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ahbot): remediate Codex review of the forged system owner (F6, F7) - F6 (critical, hardening): at GUID overflow the generator set the deferred World::StopNow flag but still returned m_nextGuid++, which at the boundary equals the reserved AH-bot system-owner GUID (0xFFFFFFFE for players) -- a real character could be branded with the forged owner's GUID before the shutdown takes effect. Return the top-of-range GUID instead (never the reserved one) and correct the false "cannot collide" comment in AhBotSystemOwner.h. (Reaching the boundary needs ~4.29 billion player GUIDs, so it is effectively unreachable -- minimal safe fix, per maintainer decision.) - F7: an upgraded ahbot.conf carrying an explicit empty AuctionHouseBot.CharacterName bypassed the forged-owner default and left the bot GUID unresolved (0). Coalesce an empty name to AHBOT_SYSTEM_OWNER_NAME so the name-intercept resolves it to the reserved system GUID. Builds clean (RelWithDebInfo). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ahworker): init BrowseQuery profile/mount fields in selftest (GCC/Clang UB) The FilterAndPaginate usable=1 selftest built a BrowseQuery but never initialized q.profile (PlayerProfile scalars) or q.minMountLevel / q.minEpicMountLevel, all of which RowUsable -> AhUsability::IsUsable reads. MSVC happened to read them as usable; GCC/Clang read uninitialized garbage and filtered every row -> "browse selftest FAILED: defer set size=0 (exp 60)" on the branch's first-ever Linux CI run. Pre-existing since SP-1 (Windows-only tested); not introduced by the Codex remediation (RowUsable / the profile / that test were untouched, and the F2 defer-branch change is behavior-identical below the cap). Fix root + symptom: - default-init the wire-struct scalars (PlayerProfile classId/raceId/level/ honorRank; BrowseQuery minMountLevel/minEpicMountLevel) so a default-constructed query can never feed uninitialized memory to the usability check; - give the selftest a capable profile (class 1, level 60) so its level-1 rows are usable deterministically on every platform. Builds clean (RelWithDebInfo); ah-service --selftest green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MadMaxMangos
added a commit
that referenced
this pull request
Jul 6, 2026
The companion SD3 and Eluna PRs were squash-merged to their masters, so re-point from the (now-redundant) feature-branch tips to the canonical master commits: SD3 7d62a5b (enhance/dbc) -> 052bd57 (mangos/ScriptDev3 master, PR #97) Eluna cc67c39 (enhance/dbc) -> f7a6e59 (MangosServer/Eluna master, PR #2) Both squash commits were verified tree-identical to the feature-branch tips.
MadMaxMangos
added a commit
that referenced
this pull request
Jul 6, 2026
#428) * CharStartOutfit: faithful DBC layout (4x uint8 identity, bbbb fmt, correct docs) Model race/class/sex/outfit as four uint8 fields (was one packed uint32), fmt 'dbbbb...' so file offsets match the 152-byte record, drop the phantom Unknown1-3, and update the single Player.cpp consumer to match on the three identity bytes. sizeof == GetFormatRecordSize == 52 (startup assert). * SpellRadius: document server-unused DBC fields (comment-but-correct) * Emotes: document server-unused DBC fields (comment-but-correct) * Lock: document server-unused DBC fields (comment-but-correct) * AuctionHouse: document server-unused DBC fields (comment-but-correct) * ItemBagFamily: document server-unused DBC fields (comment-but-correct) * MailTemplate: document server-unused DBC fields (comment-but-correct) * SpellFocusObject: document server-unused DBC fields (comment-but-correct) * WorldSafeLocs: document server-unused DBC fields (comment-but-correct) * CreatureType: document server-unused DBC fields (comment-but-correct) * CreatureDisplayInfoExtra: document server-unused DBC fields (comment-but-correct) * SoundEntries: document server-unused DBC fields (comment-but-correct) * DBC align Batch 2: 11 tables (renames + doc-unused) + consumers Renames (struct field -> client-verified provenance name), consumers updated: BankBagSlotPrices price->Price; TaxiNodes name->Name_lang; SpellCastTimes CastTime->Base; ItemClass name->ClassName_lang; CreatureSpellData spellId->SpellId; CinematicSequences Id->ID; QuestSort id->ID; Talent DependsOnSpell->RequiredSpellID; ItemRandomProperties enchant_id->Enchantment; CharStartOutfit ItemId->ItemID; DurabilityCosts Itemlvl->ID, multiplier->WeaponSubClassCost. Builds clean (mangosd links); server-unused fields documented (comment-but-correct). * DBC align Batch 3: 11 tables (28 renames + doc-unused) + consumers Renames (struct field -> client-verified provenance name; ~131 consumers updated): LiquidType Id->ID SpellId->SpellID; CreatureDisplayInfo Displayid->ID scale->CreatureModelScale; GameObjectDisplayInfo Displayid->ID filename->ModelName; SpellShapeshiftForm flags1->Flags creatureType->CreatureType; TalentTab TalentTabID->ID tabpage->OrderIndex; EmotesText Id->ID textid->EmoteID; SpellRange minRange->RangeMin maxRange->RangeMax; ChatChannels ChannelID->ID flags->Flags pattern->Name_lang; SkillLine id->ID categoryId->CategoryID name->DisplayName_lang; ChrClasses ClassID->ID powerType->DisplayPower name->Name_lang spellfamily->SpellClassSet; Map map_type->InstanceType (incl. inline helpers) name->MapName_lang linked_zone->AreaTableID multimap_id->LoadingScreenID. LiquidType.Name deferred (type-contested). Builds clean (mangosd links); server-unused fields documented. * DBC align Batch 4: 11 tables (66 renames + doc-unused) + consumers Aligns DBCStructure.h field names + docs to the client-verified schema (reconciliation.json worklist) for 11 tables: AreaTrigger, SkillRaceClassInfo, WMOAreaTable, ItemSet, WorldMapArea, SpellItemEnchantment, Faction, ChrRaces, FactionTemplate, CreatureFamily, TaxiPathNode. 66 struct-field renames + comment-but-correct docs on server-unused fields. Renames are compile-time only: same offset/bytes, sizeof unchanged, so the startup sizeof==GetFormatRecordSize assert still holds; DBCfmt.h untouched. 287 consumer sites across 37 game/playerbot files updated (compiler-verified complete: mangosd links clean). Submodule pointers advanced for the SD3 (AreaTrigger id->ID) and Eluna consumer fixes, each committed on its own enhance/dbc branch in its own repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * DBC align Batch 5: AreaTable + SkillLineAbility (19 renames + doc-unused) + consumers Final batch of the DBC field-name alignment campaign. - AreaTable (8 renames): mapid/zone/exploreFlag/flags/area_level/area_name/team/LiquidTypeOverride -> ContinentID/ParentAreaID/AreaBit/Flags/ExplorationLevel/AreaName_lang/FactionGroupMask/LiquidTypeID_3 + 9 document-unused fields (SoundProviderPref/AmbienceID/ZoneMusic/... /LiquidTypeID_0..2). - SkillLineAbility (11 renames): id/skillId/spellId/racemask/classmask/req_skill_value/ forward_spellid/learnOnGetSkill/max_value/min_value/reqtrainpoints -> ID/SkillLine/Spell/RaceMask/ClassMask/MinSkillLineRank/SupercededBySpell/AcquireMethod/ TrivialSkillLineRankHigh/TrivialSkillLineRankLow/ReqTrainPoints + 4 document-unused. - 129 consumer sites fixed across 30 game/playerbot files + 1 Eluna (submodule pointer bumped). Renames + comment-but-correct docs only; no type/width/DBCfmt change. sizeof unchanged (AreaTable 96B, SkillLineAbility 44B) so the boot size-assert still holds. Builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * LiquidType: field 1 is the Name string, not an int (fmt i->s, char* Name) * Spell: rename SpellEntry fields to build-exact 1.12 .dbd names + doc unused (struct only) * Spell: fix SpellEntry consumers for .dbd rename (align) Compiler-driven consumer fan-out for the SpellEntry struct rename (Task 2): - ~1100 member accesses across game lib + Bots + ChatCommands renamed to the build-exact 1.12 .dbd names (Id->ID, EffectApplyAuraName->EffectAura, SpellFamilyName->SpellClassSet, SpellFamilyFlags->SpellClassMask, EffectImplicitTargetA/B->ImplicitTargetA/B, SpellName->Name_lang, etc.). - Header PCH-unblock: SpellAuras.h/Spell.h/GridNotifiersImpl.h/SpellMgr.h + Bots/ItemVisitors.h inline accessors. - Explicit EffectAmplitude/EffectMultipleValue collision remap (name-swap by column the compiler cannot catch): original EffectAmplitude sites -> EffectAuraPeriod, original EffectMultipleValue sites -> EffectAmplitude. - Submodule consumer companions committed on their own enhance/dbc branches (SD3, Eluna); parent tracks the pointers. Builds clean (mangosd links). No runtime change (pure member renames). * Spell: bump SD3/Eluna to expansion-guarded accessors (cross-fork safe) Submodule consumer renames now route through expansion guards so merging the SD3/Eluna enhance/dbc branches upstream keeps One/Two/Three/Four building unchanged. SD3 4e6b444, Eluna 2d9a9a3. * AreaTrigger + SpellRange: bump SD3 pointer to guarded accessors (cross-fork safe) SD3 8f5b6ff wraps the campaign's earlier AreaTrigger id->ID and SpellRange min/max->RangeMin/RangeMax renames in expansion-guarded accessors (SD3_AreaTriggerId / SD3_SpellRangeMin / SD3_SpellRangeMax), matching the Spell/Eluna guard pattern so the SD3 enhance/dbc branch stays upstream-safe for One/Two/Three/Four. Verified by a real One/TBC mangosscript compile: all DBC-rename cross-fork breaks resolved (only a pre-existing powerType base-drift in a Karazhan script remains, unrelated to DBC alignment). SD3 8f5b6ff, Eluna 2d9a9a3 (unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Cross-fork: bump Eluna pointer to guarded Batch 3-5 consumer renames Eluna cc67c39 wraps the previously-unguarded AreaTrigger/AreaTable/ TaxiPathNode/SkillLine/ChrClasses/ChrRaces consumer renames in ELUNA_EXPANSION==EXP_CLASSIC guards (legacy token restored verbatim in #else), so merging the Eluna enhance/dbc branch upstream keeps One/Two/ Three/Four building. Verified by real game-target compiles on One (TBC) and Two (WotLK): both recompiled CreatureHooks/ServerHooks/Methods and linked game.lib with 0 errors. SD3 8f5b6ff (unchanged), Eluna 2d9a9a3 -> cc67c39. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sd3): bump pointer to cross-core GetPowerType() fix (7d62a5b) SD3 7d62a5b drops the `#if !defined(MISTS) m_creature->powerType #else GetPowerType()` guard (a master 005a340f regression that breaks TBC/WotLK/ Cata, which lack the Creature powerType data member) for plain GetPowerType() at boss_shade_of_aran + boss_deathbringer_saurfang. Verified by real mangosscript compiles: One/TBC + Two/WotLK + Three/Cata all now link with 0 errors (previously 1-2 powerType errors, no link). Surfaced by this branch's cross-fork sweep; unrelated to the DBC alignment. SD3 8f5b6ff -> 7d62a5b, Eluna cc67c39 (unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Rename SpellEntry consumers in merged-in SpellCooldownMgr (Id->ID) master's [Decomp][Player] (bfaf638) extracted the spell-cooldown code out of Player.cpp into src/game/Object/SpellCooldownMgr.cpp AFTER this branch had renamed the SpellEntry consumers - so the extracted file still read the pre-rename spellInfo->Id at 6 sites. Renamed them to spellInfo->ID (SpellEntry::Id -> ID). PetMgr uses no renamed SpellEntry fields. Post-merge validation: full mangosd build links clean (game.lib + mangosd.exe, 0 errors); SpellCooldownMgr.cpp and PetMgr.cpp now compiled into the build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Point SD3/Eluna submodules at squash-merged master commits The companion SD3 and Eluna PRs were squash-merged to their masters, so re-point from the (now-redundant) feature-branch tips to the canonical master commits: SD3 7d62a5b (enhance/dbc) -> 052bd57 (mangos/ScriptDev3 master, PR #97) Eluna cc67c39 (enhance/dbc) -> f7a6e59 (MangosServer/Eluna master, PR #2) Both squash commits were verified tree-identical to the feature-branch tips. * docs(dbc): reference structs for the 103 unmodelled client DBCs (complete 154-table map) Adds src/game/Server/DBCStructure_reference.h — inert, build-exact reference structs for the 103 client 1.12 DBC tables the core does not model, plus a catalogue of all 154 (ACTIVE in DBCStructure.h vs DOC here). Generated from the client-verified schemas by CoreAlign/tools/gen_reference.py: every field carries its .dbd name/type and a [dbd-only] flag where the schema is dbd-inferred rather than decomp-confirmed. The header is NOT #included by any TU (zero build cost); activating a table = move its struct into DBCStructure.h + add its fmt + a loader. So the core now carries a complete map of every 1.12 client DBC. Verified: valid C++ (MSVC cl /Zs, all 103 structs, 0 errors) and adversarially checked struct-vs-schema across all 103 (coverage/types/names/confidence flags). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cmangos commit bb91d5e