Nine more goldens, and four of them are realm features worth knowing

Second golden batch. Field counts were re-checked first and are still intact everywhere, so again
these are the realm's data and not a misread column.

Four are things grom would want to know about his own realm, not bookkeeping:

- **The professions need no tools.** Mining (2575) carried the Mining Pick and Skinning (8613) the
  Skinning Knife in 1.12; both are `[0, 0]` here, as are Herbalism, Blacksmithing, Cooking and
  First Aid. You gather bare-handed, and the pre-send possession check (`0x6e4000`) has nothing to
  refuse. Read rather than assumed: 362 rows still carry a tool — Pick Lock still wants its
  thieves' tools (5060) — so it is six rows blanked in the data, and the reagent columns beside
  them are untouched, which Slow Fall's Light Feather shows.
- **There is a shipped World channel**, row 27, flags `0x9` — global and auto-joined, with no `%s`
  in its pattern because there is one of it. 1.12 had no row at all; there "World" was a
  player-made channel like any other, which is exactly what the old test asserted.
- **Thalassian is implemented.** Spell 813 was "Thalassian (NYI)" declaring Common in 1.12 and
  declares language 10 here, so nine player languages have both a declaring spell and a skill line
  where 1.12 had eight. It goes with a **tenth `ChrRaces` row**, which speaks Common and is
  therefore Alliance-side.
- **Emote text ships five locales**, not four: ruRU arrived in column 5 — the column this very test
  used as its "unshipped" case. The dry-ladder case moved to 1/2/8 and the five that compose are
  asserted, so a column sliding under the ladder cannot pass unnoticed.

The rest are counts and rows: `LockType` has a fourth cursor row (id 20, "Survival", reusing
`PickLock`), `ItemBagFamily` has twelve rows with the realm's four profession bags (Meat, Fish,
Leather, Mining Supplies), `EmotesText` 169 → 171, and a fourth spell carries
`SPELL_ATTR_EX3_NO_CASTING_BAR_TEXT`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-31 18:37:57 -06:00
co-authored by Claude Opus 5
parent fbf01c486e
commit 2d745760ac
6 changed files with 109 additions and 25 deletions
+23 -3
View File
@@ -191,12 +191,21 @@ mod tests {
(23, 0x10004, "WorldDefense", "WorldDefense"),
(24, 0x00000, "LookingForGroup", "LookingForGroup"),
(25, 0x20032, "GuildRecruitment - %s", "GuildRecruitment"),
// The realm's own: a global **World** channel, which 1.12 had no row for at all —
// there it was a player-made channel like any other. Flags `0x9` is
// `0x1 | 0x8`, so it is auto-joined and global rather than zone-scoped, and its
// pattern carries no `%s` because there is one of it.
(27, 0x00009, "World", "World"),
]
);
// The auto-join set is the reference's `chat-cache.txt` `ZONECHANNELS` mask.
// The auto-join set is the reference's `chat-cache.txt` `ZONECHANNELS` mask. World joins
// itself too, so the mask gains bit 26.
let mask: u32 = cat.auto_join_rows().map(|r| 1 << (r.id - 1)).sum();
assert_eq!(mask, 0x0020_0003, "General + Trade + LocalDefense");
assert_eq!(
mask, 0x0420_0003,
"General + Trade + LocalDefense + the realm's World"
);
}
#[test]
@@ -234,6 +243,17 @@ mod tests {
assert_eq!(cat.zone_channel_id("General - Elwynn Forest"), 1);
assert_eq!(cat.zone_channel_id("Trade - City"), 2);
assert_eq!(cat.zone_channel_id("LocalDefense - Durotar"), 22);
assert_eq!(cat.zone_channel_id("World"), 0, "a custom channel is 0");
// "World" is a **shipped** channel here (row 27), not a player-made one, so it resolves.
assert_eq!(
cat.zone_channel_id("World"),
27,
"the realm's World channel"
);
// A genuinely player-made name still answers 0, which is what that check is for.
assert_eq!(
cat.zone_channel_id("GromsPartyChat"),
0,
"a custom channel is 0"
);
}
}
+32 -2
View File
@@ -224,7 +224,8 @@ mod tests {
let data = crate::wow_data_or_skip!();
let mut chain = Chain::open(&data).expect("open patch chain");
let cat = load_emote_text_catalog(&mut chain).expect("load");
assert_eq!(cat.len(), 169, "one form table per EmotesText row");
// 171, where 1.12 shipped 169: the realm adds two emotes.
assert_eq!(cat.len(), 171, "one form table per EmotesText row");
// ctx 0: someone else waves at someone else.
assert_eq!(
@@ -298,12 +299,41 @@ mod tests {
}
/// An unshipped locale is empty everywhere, so the ladder runs dry: no line (`0x49b4bd`).
///
/// Which columns are shipped moved with the realm: it fills **five** locales where 1.12's
/// enGB chain filled four. Column 5 (ruRU) is the one that arrived — this test used to use it
/// as its empty case — so the dry ladder is now shown on 1, 2 or 8, and the five that compose
/// are asserted so a column sliding under the ladder cannot pass unnoticed.
#[test]
fn an_unshipped_locale_composes_nothing() {
let data = crate::wow_data_or_skip!();
let mut chain = Chain::open(&data).expect("open patch chain");
let cat = load_emote_text_catalog(&mut chain).expect("load");
assert_eq!(cat.compose(WAVE, &line("Bob", "Jane", "Me"), 5), None);
for empty in [1, 2, 8] {
assert_eq!(
cat.compose(WAVE, &line("Bob", "Jane", "Me"), empty),
None,
"locale {empty} ships nothing, so the ladder runs dry"
);
}
for (loc, want) in [
(0usize, "Bob waves at Jane."),
(3, "Bob winkt Jane zu."),
(5, "Bob машет Jane рукой."),
(6, "Bob saluda con la mano a Jane."),
(7, "Bob acena para Jane."),
] {
assert_eq!(
cat.compose(WAVE, &line("Bob", "Jane", "Me"), loc)
.as_deref(),
Some(want),
"locale {loc}"
);
}
// zhCN composes too; its text is asserted apart from the Latin ones for legibility.
assert!(cat
.compose(WAVE, &line("Bob", "Jane", "Me"), 4)
.is_some_and(|s| s.contains("Bob") && s.contains("Jane")));
assert_eq!(cat.compose(9999, &line("Bob", "Jane", "Me"), 0), None);
}
+6 -1
View File
@@ -84,6 +84,11 @@ mod tests {
assert_eq!(cat.name(4), None);
assert_eq!(cat.name(5), None);
assert_eq!(cat.name(0), None, "family 0 must not render as \"NONE\"");
assert_eq!(cat.len(), 8, "the whole shipped table");
// The realm's four: the profession bags 1.12 never shipped a family row for.
assert_eq!(cat.name(10), Some("Meat"));
assert_eq!(cat.name(11), Some("Fish"));
assert_eq!(cat.name(12), Some("Leather"));
assert_eq!(cat.name(13), Some("Mining Supplies"));
assert_eq!(cat.len(), 12, "the whole shipped table");
}
}
+8 -2
View File
@@ -264,8 +264,14 @@ mod tests {
let mut chain = Chain::open(&data).expect("open patch chain");
let langs = load_default_languages(&mut chain).expect("load");
// All nine `ChrRaces` rows resolve: the eight playable races and the Goblin.
assert_eq!(langs.len(), 9, "one entry per ChrRaces row");
// Ten `ChrRaces` rows resolve, where 1.12 had nine: the eight playable races, the Goblin,
// and the realm's own tenth race, which speaks **Common** — so it is Alliance-side.
assert_eq!(langs.len(), 10, "one entry per ChrRaces row");
assert_eq!(
langs.name(10, 0),
Some("Common"),
"the realm's tenth race is Alliance-side"
);
// Alliance speaks Common, Horde Orcish; the racial language would give Darnassian.
for race in [1, 3, 4, 7] {
assert_eq!(langs.name(race, 0), Some("Common"), "race {race}");
+6 -2
View File
@@ -100,10 +100,14 @@ mod tests {
assert_eq!(cat.cursor_name(13), None);
assert_eq!(cat.cursor_name(19), None); // Fishing
assert_eq!(cat.cursor_name(0), None); // an empty lock slot's index
// A fourth row carries one now: id 20, "Survival", reusing the `PickLock` cursor. It is
// the realm's own lock type, and the cursor is what a player sees on a skinnable corpse.
assert_eq!(cat.cursor_name(20), Some("PickLock"));
assert_eq!(cat.name(20), Some("Survival"));
assert_eq!(
cat.len(),
3,
"only three LockType rows carry a CursorName in 5875"
4,
"four LockType rows carry a CursorName on this realm, three in 5875"
);
// A slip to `ResourceName` (field 10) would print "Requires Herbs".
@@ -217,7 +217,7 @@ fn real_spell_catalog_reads_open_lock_types() {
);
// The cast bar shows no name for `AttributesEx3 & 0x4` (`SPELL_ATTR_EX3_NO_CASTING_BAR_TEXT`),
// the one column separating 22810 from 6478; three shipped rows carry it.
// the one column separating 22810 from 6478; four shipped rows carry it (three in 5875).
assert!(cat.get(22810).unwrap().no_casting_bar_text());
assert!(!cat.get(6478).unwrap().no_casting_bar_text());
let silent: Vec<u32> = {
@@ -231,21 +231,36 @@ fn real_spell_catalog_reads_open_lock_types() {
};
assert_eq!(
silent,
vec![6477, 22810, 26380],
"6477 Opening / 22810 Opening - No Text / 26380 zzOLDSummon Mouth Tentacle Visual"
vec![6477, 22810, 26380, 47405],
"6477 Opening / 22810 Opening - No Text / 26380 zzOLDSummon Mouth Tentacle Visual, plus \
one of the realm's own (47405) — four rows carry the attribute, three in 5875"
);
// The tool and reagent columns the pre-send possession check reads (`0x6e4000`): totems at
// columns 40-41, reagents at 42-49 with their counts at 50-57.
// **The realm removed the professions' tool requirements.** Mining (2575) carried the Mining
// Pick (2901) in 1.12 and Skinning (8613) the Skinning Knife (7005); both are `[0, 0]` here,
// as are Herbalism, Blacksmithing, Cooking and First Aid. So you mine and skin bare-handed on
// this realm, and the pre-send possession check (`0x6e4000`) has nothing to refuse.
//
// Read, not assumed: the column still works. 362 rows carry a tool — Pick Lock's thieves'
// tools (`(1804, [5060, 0])`), the lockpicking trainer ranks, Rogue poisons at 11130 — so
// this is six rows blanked in the data, not a column slip. The reagent columns beside them
// are untouched, which `Slow Fall` below shows.
assert_eq!(
cat.get(2575).unwrap().totems,
[2901, 0],
"Mining requires the Mining Pick (2901)"
[0, 0],
"Mining needs no Mining Pick on this realm"
);
assert_eq!(
cat.get(8613).unwrap().totems,
[7005, 0],
"Skinning requires the Skinning Knife (7005)"
[0, 0],
"Skinning needs no Skinning Knife on this realm"
);
assert_eq!(
cat.get(1804).unwrap().totems,
[5060, 0],
"…while Pick Lock still wants its thieves' tools (5060), so the column is read"
);
let slow_fall = cat.get(130).unwrap();
assert_eq!(
@@ -1177,12 +1192,15 @@ fn the_language_declaring_spells_cover_nine_of_thirteen_languages() {
// (spell, declared language, SkillLine.dbc id): every SPELL_EFFECT_LANGUAGE row but 25674.
const DECLARED: &[(u32, u32, u32)] = &[
(668, 7, 98), // Common
(669, 1, 109), // Orcish
(670, 3, 115), // Taurahe
(671, 2, 113), // Darnassian
(672, 6, 111), // Dwarvish
(813, 7, 137), // "Thalassian (NYI)", declaring Common
(668, 7, 98), // Common
(669, 1, 109), // Orcish
(670, 3, 115), // Taurahe
(671, 2, 113), // Darnassian
(672, 6, 111), // Dwarvish
// The realm **implemented** Thalassian: 1.12's row declared Common (7) because the
// language was not in, and this one declares 10, the `Languages.dbc` Thalassian row. That
// goes with its tenth playable race (see `languages.rs`).
(813, 10, 137), // Thalassian, declaring Thalassian
(814, 7, 138), // "Draconic (NYI)", declaring Common
(815, 7, 139), // Demon Tongue, declaring Common
(816, 7, 140), // "Titan (NYI)", declaring Common
@@ -1224,8 +1242,9 @@ fn the_language_declaring_spells_cover_nine_of_thirteen_languages() {
.collect();
assert_eq!(
understandable,
[1, 2, 3, 6, 7, 13, 14, 33].into_iter().collect(),
"only the eight player languages have both a declaring spell and a skill line"
[1, 2, 3, 6, 7, 10, 13, 14, 33].into_iter().collect(),
"nine player languages here, where 1.12 had eight: Thalassian (10) now has both a \
declaring spell and a skill line, which is what implementing it means"
);
// An ordinary ability declares nothing.