Files
benilla-octo/crates/benilla-formats/src/game_tips.rs
T
Sam SungandClaude Opus 5.5 8505187668 Comments in benilla-formats: one line of fact each, no history, no record numbers (#130)
Part of #41: the comments of `benilla-formats` (the library, its extract tools, examples and tests) are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 19,654 comment lines become 7,264.

Fixes #105: the WMO collision comments now name walking's box-query leaf `0x6bca50` and the camera and line-of-sight segment-query leaf `0x6bc700`, in `models/collision.rs`, `examples/wmo_bsp.rs` and `tests/chain.rs`.

- **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal.
- **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them:
  - A placed doodad's material alpha reads file slot 0 on its spawn clock, while the reference reads bone 0's armed sequence, re-rolled every play window. The old text said doodads are armed once at load.
  - The fog end is stored ×36 and scaled once at load (`0x6d6090`). It is not raw.
  - The MCSH bit picks a map doodad's sun scale, 1.0 lit or 0.5 shadowed. 2.5 belongs to an entity's light node.
  - `SpellRec` is the row as stored, column × 4, not a translated copy.
  - A global sequence's clock is anchored at the instance's attach time (`0x714352`).
  - Particle twinkle is a gated size multiplier (`0x7b2adc`).
  - The blob shadow sizes from the Stand box.
  - Data claims are checked against the install: the weapon-trail kits' parameters and alphas, the beam rows' noise, and the maps' `field_16`.
  - Dozens more turned up on the way: renamed or deleted items, docs sitting on the wrong item, stale counts and links.
- **Deviation labels.** "Deviation:" now marks only a choice with a reason. An unbuilt behaviour says so plainly.
- **Verification.** Every file strips to identical code, and `cargo fmt --check` is clean. Four second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the reference, the code and the shipped data.

String literals are untouched; they are #92.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 19:38:53 +07:00

90 lines
3.0 KiB
Rust

//! `GameTips.dbc`: the loading-screen tips, each with its own `|cffffd100Tip:|r` prefix and a
//! trailing `\r\n` in the data; FrameXML never reads them. The reference loads them in file order
//! into one array (`[0xc0dcd0]`, count `[0xc0dcd4]`, loader `0x545f90`), and
//! `CGlueMgr::EnterWorld` (`0x46b500`) stores the chosen record index, never an id, in the
//! `gameTip` CVar. Record order is not id order: the file opens on id 396.
use anyhow::{Context, Result};
use benilla_dbc::{FieldType, Schema, SchemaField};
use crate::chain::Chain;
use crate::dbc::{parse, str_at};
const GAME_TIPS: &str = "DBFilesClient\\GameTips.dbc";
/// The tips in file record order, the order the persisted `gameTip` index counts through.
#[derive(Clone, Debug, Default)]
pub struct GameTipsCatalog {
tips: Vec<String>,
}
impl GameTipsCatalog {
/// A catalog over given tips, for a test without an install.
pub fn from_tips(tips: Vec<String>) -> Self {
GameTipsCatalog { tips }
}
/// The tip at a record index; an index saved under another chain can fall past the table.
pub fn get(&self, index: usize) -> Option<&str> {
self.tips.get(index).map(String::as_str)
}
pub fn len(&self) -> usize {
self.tips.len()
}
pub fn is_empty(&self) -> bool {
self.tips.is_empty()
}
}
fn schema() -> Schema {
let mut s = Schema::new("GameTips");
s.add_field(SchemaField::new("ID", FieldType::UInt32));
s.add_field(SchemaField::new_array("Text", FieldType::String, 8));
s.add_field(SchemaField::new("TextMask", FieldType::UInt32));
s.set_key_field("ID");
s
}
pub fn load_game_tips(chain: &mut Chain) -> Result<GameTipsCatalog> {
let bytes = chain.read_file(GAME_TIPS).context("reading GameTips.dbc")?;
let rs = parse(&bytes, schema(), "GameTips")?;
let mut tips = Vec::with_capacity(rs.records().len());
for r in rs.records() {
// A row with an empty text slot is dropped, not shown blank; the shipped file has none.
match str_at(&rs, r, 1) {
Some(text) if !text.trim().is_empty() => tips.push(text),
_ => {}
}
}
Ok(GameTipsCatalog { tips })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_real_table_is_seventy_four_tips_in_file_order() {
let data = crate::wow_data_or_skip!();
let mut chain = crate::open_chain(&data).expect("open chain");
let cat = load_game_tips(&mut chain).expect("load GameTips");
assert_eq!(cat.len(), 74, "the 5875 enGB chain ships 74 tips");
assert!(
cat.get(0).unwrap().starts_with("|cffffd100"),
"the colour prefix is authored INTO the data, not added by the renderer: {:?}",
cat.get(0)
);
assert!(
cat.get(0).unwrap().contains("mini-map"),
"record order, not id order — record 0 is id 396: {:?}",
cat.get(0)
);
assert!(
cat.get(74).is_none(),
"one past the end is None, not a panic"
);
}
}