Sniff verification across 207k retail packets (11 sessions, builds
65940-67186) confirmed all 22 SMSG opcodes in our internal 0xF1000xxx
placeholder range never appear in retail. Cross-checked against the
auto-generated Lua API docs in wow-ui-source-12.0.5 — none have a
corresponding C_Housing*/C_HouseEditor/C_NeighborhoodInitiative Lua
function or event. Either the feature uses entity-fragment updates
(like the *Added family), uses an existing real opcode that already
covers the same state, or doesn't exist in retail at all.
Two of the 22 had real opcodes hidden in IDA/comment metadata:
HousingUpdateHouseInfo remapped 0xF1000012 -> 0x550004
HousingCatalogStateSync remapped 0xF1000002 -> 0x56000E
(also reclaims 0x56000E from a
misnamed SMSG_LFG_LIST_UPDATE_BLACKLIST
placeholder with 0 emit-sites; wire
decoded as 4+N*8 catalog entries
matching the documented PackedState
bit layout)
The other 20 retired classes + their emit sites are removed entirely
(retirement-marker comments preserve any IDA-derived opcode hints +
wire shapes for future restoration if real opcodes get discovered):
4 *Added family (Room/Fixture/Theme/RoomComponentTexture)
10 Group A zero-emit-site classes in 0xF1000008..0xF1000010
(HousingSetHouseNameResponse, HousingSvcs{Create,GetDetails,
GetHouses,HouseExpiration,Move,Search,SetSettings,Swap}*,
InitiativeTrackedUpdated)
4 SMSG_INITIATIVE_* (UpdateStatus, PointsUpdate, MilestoneUpdate,
ChestResult) + their 8 InitiativeManager emit sites — real
opcodes 0x420364/65/66/68/69/6A/6B already cover every state
transition
4 Decor* speculative responses (BatchOperation, CatalogCreateSearcher,
PlacementPreview, StartPlacingNewDecor) — Lua-API-verified to have
no retail counterpart (HousingCatalogSearcherAPI is purely
client-side; StartPlacingNewDecor is fire-and-forget)
HousingEditorAvailabilityResponse — C_HouseEditor.GetHouseEditorAvailability
returns synchronously, no server roundtrip
HousingSystemHouseSnapshotResponse — no C_HouseSnapshot Lua namespace
exists in retail 12.0.5
Additional behavioral fixes layered in along the way:
- InitiativeManager: wire decor + quest rewards on milestone claim
(was TODO no-op; Housing::AddToCatalog + Player::RewardQuest)
- Post-tutorial auras gated on quest 94455 completion in both
HousingMap and HouseInteriorMap (was unconditional, applied to
pre-tutorial players)
- BuyHouse starter favor (910) now persists server-side via
Housing::AddFavor with new emitUpdate=false param to suppress
AddFavor's own packet; the existing sniff-shaped 2-packet
HousingSvcsUpdateHousesLevelFavor sequence then reads h->GetFavor()
- Duplicate CMSG_HOUSING_SVCS_RELINQUISH_HOUSE registration deleted
Diff:
13 files changed
+204 / -810 = 606-line net deletion
Build-verified clean against the 12.0.5 source tree with VS17 2022
RelWithDebInfo (-j 2 to avoid MSVC PCH virtual-memory exhaustion at
higher parallelism). No real code errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Signed-off-by: luis <[email protected]>
Worldserver crashed at startup after applying 2026_04_29_00_hotfixes.sql
with mysql_stmt_prepare() id 249 "Unknown column 'FileDataID' in 'field
list'" — the C++ PrepareStatement was still on the older WoW-build
column set (FileDataID/ConditionID/HookID/Slot/SortOrder/
ComponentGroupID/UiTextureKitID/ExteriorComponentTypeID) while the SQL
table is now on the 12.0.5 layout (ParentComponentID/ModelFileDataID/
Flags/Field_7/Type/Field_9/GameObjectID/Field_11/ItemID/
HouseExteriorWmoDataID).
Updated the SELECT to match DB2LoadInfo::ExteriorComponentLoadInfo
column order (LayoutHash 0x53EA0925, 14 fields). The in-memory
ExteriorComponentEntry struct in DB2Structure.h was already on the new
layout — only the prepared statement lagged.
Sniff-decoded from C:\sniff\housing_stuff\{alliance,horde}_housing\
dump_12.0.5.67186_2026-04-28_*.pkt: when a player clicks a housing
door GO the 12.0.5 retail client emits CMSG_CAST_SPELL with
SpellID 1,271,876 (uint32 at body offset 0x15) targeting the door.
Both faction sniffs are byte-identical in the spell-id field; only the
target PackedGUID at offset 0x3D differs. CMSG_GAME_OBJ_REPORT_USE that
follows is criteria-tracking, NOT the trigger — the cast is.
The server side had four stacked gaps blocking this flow:
A. SpellID 1271876 was missing from spell_name / spell_misc / spell_effect.
Cast validation rejected it as unknown. Added a minimal row set with
SPELL_EFFECT_DUMMY targeting TARGET_GAMEOBJECT_TARGET so the cast
accepts and the SpellScript hook fires.
B. gameobject_template.Data10 (goober.spell) on the housing front-door
templates was either zero (575017, 602702) or pointing at the older
12.0.1 spell 1234192 (586576, 602705) / 1234193 (587318). Neither
matches what the 12.0.5 retail client casts, so the goober mechanism
wouldn't have routed correctly even if the spell existed. All five
entries now point at 1271876.
C. New SpellScript spell_housing_door_open, registered against 1271876
via spell_script_names, intercepts the cast and calls
GameObject::Use() on the spell target. That routes to the existing
go_housing_door::OnGossipHello which already handles edit-mode
gating, visitor permissions, and the interior round trip.
D. The exterior_component SQL hotfix table was on an older WoW build's
schema (FileDataID/ConditionID/HookID/Slot/SortOrder/...) that
doesn't line up with DB2LoadInfo's 12.0.5 layout
(Name + 3 floats + ID + Size + ParentComponentID + ModelFileDataID +
Flags + Field_7 + Type + Field_9 + GameObjectID + Field_11 +
ItemID + HouseExteriorWmoDataID). Without a GameObjectID column the
hotfix loader was filling that field with zeros, so HousingMap::
SpawnExtCompTree never spawned door GOs in the first place. Schema
rebuilt to match the 14-field 12.0.5 record (LayoutHash 0x53EA0925).
Tester noted PropsID 170 has Accuracy(param[1]) and IsPeriodic(param[4])
labels that weren't visible in the WEATHER_SET handler.
- Accuracy is already rolled at the top of ProcessEffect before the
switch dispatches; added a comment so the handler doesn't look
unwired.
- IsPeriodic was silently ignored. PetBattleEnvironment now carries
PeriodicStateIDs; WEATHER_SET inserts when the flag is set;
TickWeather re-emits SET_STATE each round for periodic states so
the client refreshes its visual/counter. ClearWeatherStates also
clears the periodic set.
Tester log shows a Squirrel (creature 61081, species 379) entering
combat at Level=0:
PetBattle LoadWildPetAbilities: Species=379 Level=0 entries=6 loaded=0
PetBattle GenerateWildTeamInput: NO available abilities! ... -> PASS
Every BattlePetSpeciesXAbility entry has RequiredLevel >= 1, so the
level gate (RequiredLevel > pet.Level with pet.Level = 0) filtered all
six entries out. The wild pet then idled every round because the AI's
ability-collection loop produced an empty list.
Root cause is that creature->GetWildBattlePetLevel() can legitimately
return 0 — SelectWildBattlePetLevel only assigns a level when
IsWildBattlePet() is true at the moment it runs (creature spawn /
respawn), and we've seen cases (summoned creatures, hot-respawned
spawns, zones with no AreaTable.WildBattlePetLevelMin entry) where
that path doesn't fire. Clamp the value at the read site in
GenerateWildTeam, log a WARN with the creature entry so the underlying
spawn data can be fixed, and reuse the clamped value for the +/-1
variation that derives extra wild pets in multi-pet wild battles.
Marcus Jensen (npc 63014) gives the four MoP intro pet-battle quests
which all use Type=0 MONSTER objectives against virtual kill-credit
creatures rather than QUEST_OBJECTIVE_CRITERIA_TREE. Without explicit
KilledMonsterCredit calls these quests never advance even though our
CriteriaType progress lines up.
- 31308 "Learning the Ropes" -> creature 65355 on any pet battle win,
fired in PetBattle::FinishBattle for the winning team.
- 31550 "Got one!" -> creature 65356 on first capture, fired in
PetBattle::CompleteBattle next to the existing AccountObtainPet /
PlayerObtainPet criteria.
- 31785 "Level Up!" -> creature 65876 on first 2->3 transition,
fired inside the level-up loop in BattlePetMgr::GrantBattlePetExperience
(only at level == 3 so larger XP grants don't double-credit).
- 31309 "On The Mend" -> creature 64320 when Revive Battle Pets
actually heals at least one pet, fired in
BattlePetMgr::HealBattlePetsPct after the updates list is non-empty
so casting the spell with full-HP pets doesn't credit.
KilledMonsterCredit no-ops if the player has no quest with that
objective, so each call is safe regardless of quest state.
Three tester-reported bugs in one pass:
1. Multi-round aura icon was missing because AURA_APPLY/AURA_CHANGE
wire param 0 (AbilityID — used by the client to look up the spell
icon) carried the parent cast ability instead of the actual aura
ability. When a wrapper ability applies a sub-aura via
AuraBattlePetAbilityID, the cast ability has no icon and the slot
came up blank. Use AuraBattlePetAbilityID when set, fall back to
the cast ability ID otherwise. Stored on the aura so AURA_CHANGE
and AURA_CANCEL ticks pick up the same ID.
2. Weather auras (Call Darkness etc.) were landing on the casting
pet with a turn counter instead of in the middle environment slot.
Root cause: BuildEffectActionMap's weather label match was an
exact string set ("weatherState"/"WeatherState"/"weatherAura"/
"WeatherAura"). Labels like "WeatherStateID" missed the weather
bucket and fell into the "State" substring branch, so the parent
ability never got into _weatherAbilityIDs and IsWeatherAbility
returned false at runtime. Replace with a case-insensitive
"weather" substring check that runs before the State/etc. checks.
3. Wild battles where the player captured the only opponent did not
credit DEFEATBATTLEPET quest objectives because the captured pet
stayed alive (Health > 0). Capture removes the pet from play just
like a kill, so credit it too.
Also raise GenerateWildTeamInput's "no available abilities" log from
DEBUG to WARN ("battlepet" channel) so the next time a tester sees a
wild pet idle, the species ID and ability slots show up in the log
without needing to flip the global log level.