-- DopingControl core/sim.lua -- Test mode: deterministic simulated raid. -- DC_Sim.buildStore() returns a store in EXACTLY the shape the real scan -- produces (source="sim") -- model, aggregation and -- rendering run unchanged on it, so test mode exercises the real pipeline. -- -- Fixed synthetic roster: 40 players (a FULL raid -- this server's raid -- cap is 40, eight subgroups of five), all 9 classes, role distribution -- TANK 4 / MELEE 11 / RANGED 6 / CASTER 11 / HEALER 8. No randomness -- -- every run looks the same, screenshots are comparable. All names start -- with "Sim" so no synthetic name can be mistaken for (or whispered to) a -- real player; chat outputs are dead in test mode anyway. Every player -- also carries subgroup 1..8 (five each, real raid-group shape) so the -- full-raid layout -- scroll behavior, group headers, column widths, the -- render cost of forty rows -- is exercised at MAXIMUM occupancy. -- -- Coverage built into the roster (every feature is showcased): -- * every cell state in every tab (HAS / MISSING / UNKNOWN / NOTEXP) -- * TANK group all readable with 0 gaps in all three tabs (green pill) -- * exactly 3 offline + 2 read-failed players (5 of 40 unreadable); the -- read-failed carry distance values (87 / 203) as diagnosis. All 5 -- unreadable players share subgroup 8 -- a raid subgroup that is -- ENTIRELY unreadable, the opposite pill extreme from the all-green -- TANK group above (both a fully-clean and a fully-blank group must -- render correctly). -- * 2 unconfirmed roles (Simfury, Simshade -> suggestion badges) -- * trinkets: epic (quality 4), rare (quality 3), one EMPTY slot 14 -- * enchant gaps (enchant==0), EMPTY off hand (fury warrior), wand -- wearer (ranged enchant NOTEXP), holdable off hand (NOTEXP) -- * HAS via spell-ID match for some players (names empty) and via -- name match for others (ids empty) -- both model paths are exercised -- * whisper-eligible rows (readable + gaps) in every tab -- * FULL icon coverage: every HAS-producing -- aura name has a real-looking vanilla icon path in auras.textures -- (built programmatically below, so id-matched and name-matched -- players are both covered), and every gear entry TABLE carries -- texture + name + quality -- test mode showcases the icon feature. -- * DEBUFFS tab: exactly 3 afflicted players -- -- Simrogue + Simtree share "Resurrection Sickness" (afflicted=2 => -- first column, count sorting testable), Simfrost carries "Curse of -- Recklessness" + "Weakened Soul" (two columns from ONE player) => -- 3 columns total, count-desc then alpha: RS, CoR, WS. Every other -- READABLE player gets an EMPTY debuffs table (legitimate clean state -- = HAS cells); the 5 unreadable players keep debuffs nil (UNKNOWN). -- * far flag: 2 READABLE players beyond DC.FAR_LIMIT=40 -- (Simhuntara 55, Simfrost 120) => coverage.far == 2; Simbow keeps -- distance 31 (near), the read-failed distances 87/203 stay pure -- diagnosis and never count as far. -- * weapon enchant names: two players carry -- weaponMainName -- Simtank "Dense Sharpening Stone", Simmage -- "Brilliant Wizard Oil" (WPN HAS detail = the name); Simwall and -- every one of the 40-man fill-in players below stay weaponMain=true -- WITHOUT a name (generic detail path) -- exactly two named enchants -- in the whole roster, deliberately. -- * last-known showcase: Simblade (offline) carries -- lastAuras + lastGear (At = now-540) so all three aura/equipment -- tabs show dimmed last-known cells (HAS and MISSING both reachable; -- the cached EMPTY off hand classifies NOTEXP => that cell stays a -- plain "?"). Simhuntara is THE cross-continent case: online and -- aura-readable but gearRead=false + gear nil, with lastGear -- (At = now-1200) -- EQUIPMENT renders the "out of range" row note -- plus dimmed tiles. IRON RULE: all of it is display-only, the cells -- stay UNKNOWN in every count. -- * deterministic clock: all At offsets and scanAt hang -- off ONE base `now` -- GetTime() when the client provides it (test -- mode shows plausible live ages), a FIXED 1000 offline so buildStore -- stays byte-deterministic under the test harness. -- * RESIST tab: every readable player carries resistsRead=true + a -- resists table -- the 3 tanks fire 315/240/180 (a visible spread), -- Simhuntara nature 120 (resist reads work even with gearRead=false -- -- an independent channel), Simmage frost 85 + arcane 45 (the two- -- school showcase), everyone else a small deterministic per-school -- formula (0s occur -- a legitimate resistance, distinct from the 4 -- unreadable players who carry no resist data at all -> UNKNOWN -- cells). Simtank also carries a real NECK enchant (>0) -- neck/ring -- enchants are a server-custom feature (data/enchants.lua) whose -- expectation defaults to OFF, so this doubles as the "not expected, -- item still shown" showcase. -- * HIT tab (its own tab, eight columns: melee, ranged and one per -- magic school): every showcase of the feature is in the roster -- -- MEASURED skill Simtank (skillExact 305 => +1.0% hit on the -- player's side: melee 8 gear -> 9.0 against the flat 8.0 yellow -- cap), ESTIMATED racial skill Simwall/Simsteel/Simrogue -- (human/orc/gnome specializations), BOOK candidate Simsteel (ticking -- "Axes" in the options adds +3 skill, worth +1.6 on his value, on -- the next refresh -- the cap stays flat at 8.0 because skill counts -- on the player's side, and the flag lives in the DB, never in this -- store), AT CAP -- Simtank/Simsteel/Simbow (melee + ranged), BELOW CAP Simwall (6) -- and Simfrost (spell 8 of 16), DUAL WIELD Simrogue (dagger + sword: -- yellow cap 8.0 met by 10, white cap 27.0), NO RANGED WEAPON -- Simfrost (empty ranged slot) and every wand user (wands are a -- spell-hit question) => NOTEXP cells, UNREADABLE Simhuntara -- (hit = { hitRead = false } -- the item tooltips of an out-of-range -- unit cannot be scanned, exactly like her gear) plus the offline/ -- read-failed players, who carry no hit table at all. -- * PER-SCHOOL spread, the reason the tab exists: Simmage is a frost -- mage with Arcane Focus 5/5 and Elemental Precision 3/3, so her -- eight columns are NOT one number -- arcane 20.0 (10 generic + 10 -- talent), fire and frost 16.0 (10 + 6), holy/nature/shadow 10.0. -- Simlock carries the GEAR side of the same idea: a +3% shadow item -- line, so his shadow column reads 3 above his other schools. -- * TALENT READABILITY: a foreign player's talents arrive over the -- inspect protocol (scan/talents.lua) and may simply not be in yet, -- which is why hit.talentsRead exists and why a tooltip without them -- says the number excludes talents for now. Test mode deliberately -- marks a HANDFUL of players talent-readable (Simtank, Simrogue, -- Simmage) and leaves the rest false, so both tooltip states are -- visible side by side in one screenshot -- it is simulated data, not -- a claim that foreign talents are readable. Those three carry a -- MEASURED weapon skill as well, because live both facts have the -- same single source: being the own character. -- Everyone else gets a deterministic filler pass (per-class main -- weapon, ranged subtype from their equipped ranged item, small -- per-index numbers, no race => plain base skill, talentsRead false). -- All of it is display-only: a hit cell is never a gap, so pills stay -- untouched. -- * FULL-RAID FILL-IN (the fifteen players that take the roster from the -- original 25-player sample to the 40-man cap, one per open role -- slot): each is deliberately "clean" -- full consumables/class buffs -- via one canonical name list (unexpected slots simply ignore names -- they do not need, never a gap), default gearWith() (every enchant -- slot filled, both trinkets present), weaponMain=true without a -- name. They ride the SAME index-based filler passes below as every -- other filler player in the original 25 for resist/hit/debuffs, so -- their exact numbers are deliberately UNPINNED (only their existence -- and readability matter) -- every pinned showcase value above still -- lives on the unchanged original 25. Two of them (Simarrow, -- Simscout) carry a real ranged weapon so the RANGED group's HIT tab -- is not entirely NOTEXP; Simveil is the 3rd offline case. Column- -- width stress: Simox is the shortest name in the roster (5 -- characters), Simthornweaver and Simshadowmender the longest -- (14 / 15). -- Pure Lua 5.0, no WoW API -- dofile-loadable offline. DopingControl = DopingControl or {} local DC = DopingControl DC_Sim = DC_Sim or {} -- ------------------------------------------------------------------ -- Built-in role confirmations. Everyone EXCEPT Simfury and Simshade -- -- those two stay unconfirmed so the suggestion flow (dashed badge, ?-mark, -- reason tooltip) is visible in test mode. Copied into DC.simRoles by -- buildStore(); NEVER into db.roles. -- Simsham is a confirmed MELEE (enhancement) although the class heuristic -- suggests HEALER -- showcases confirmed-beats-suggested. -- ------------------------------------------------------------------ DC_Sim.ROLES = { Simtank = "TANK", Simwall = "TANK", Simsteel = "TANK", Simrogue = "MELEE", Simblade = "MELEE", Simstab = "MELEE", Simkitty = "MELEE", Simsham = "MELEE", Simhuntara = "RANGED", Simbow = "RANGED", Simtrue = "RANGED", Simmage = "CASTER", Simfrost = "CASTER", Simzap = "CASTER", Simlock = "CASTER", Simafflic = "CASTER", Simboom = "CASTER", Simheala = "HEALER", Simlight = "HEALER", Simtree = "HEALER", Simchain = "HEALER", Simholy = "HEALER", Simglow = "HEALER", -- Simfury, Simshade intentionally absent -> suggestion badges -- Full-raid fill-in (25 -> 40): one open role slot each, all -- confirmed (no additional suggestion badges beyond the two above). Simblock = "TANK", Simgrit = "MELEE", Simslash = "MELEE", Simox = "MELEE", Simjolt = "MELEE", Simzeal = "MELEE", Simarrow = "RANGED", Simscout = "RANGED", Simveil = "RANGED", Simember = "CASTER", Simdread = "CASTER", Simvoid = "CASTER", Simthornweaver = "CASTER", Simshadowmender = "HEALER", Simbloom = "HEALER", } -- ------------------------------------------------------------------ -- Small builders (fresh tables on every call -- the store must never share -- table references between builds or players). -- ------------------------------------------------------------------ -- list -> set local function set(list) local t = {} for i = 1, table.getn(list) do t[list[i]] = true end return t end -- ------------------------------------------------------------------ -- Canonical "clean" aura set for the 40-man fill-in players (see the -- header): one name per consumable/class-buff slot, always paired with -- a working ICONS entry (icon coverage stays satisfied). A slot the -- player's role/class does not expect simply ignores its name -- an -- unexpected slot is NOTEXP regardless of what auras are present -- so -- one shared list is safe to reuse across every role and class. -- cleanAuras() returns a FRESH table on every call (set() already builds -- a new table each time); the source list itself is read-only. -- ------------------------------------------------------------------ local CLEAN_AURA_NAMES = { "Flask of the Titans", "Well Fed", "Juju Might", "Juju Power", "Elixir of the Mongoose", "Greater Arcane Elixir", "Mageblood Potion", "Elixir of Superior Defense", "Elixir of Fortitude", "Greater Fire Protection", "Mark of the Wild", "Power Word: Fortitude", "Shadow Protection", "Arcane Intellect", "Soulstone Resurrection", } local function cleanAuras() return { names = set(CLEAN_AURA_NAMES), ids = {} } end -- ------------------------------------------------------------------ -- Aura icons: plausible vanilla icon paths for every -- buff name the sim can turn into a HAS cell. Cosmetic sim data -- paths -- only have to LOOK real, the client never loads them offline. -- ------------------------------------------------------------------ local ICONS = { -- FLASK ["Flask of the Titans"] = "Interface\\Icons\\INV_Potion_62", ["Flask of Supreme Power"] = "Interface\\Icons\\INV_Potion_41", ["Flask of Distilled Wisdom"] = "Interface\\Icons\\INV_Potion_97", ["Flask of Chromatic Resistance"] = "Interface\\Icons\\INV_Potion_48", -- FOOD ["Well Fed"] = "Interface\\Icons\\Spell_Misc_Food", ["Dragonbreath Chili"] = "Interface\\Icons\\INV_Drink_17", -- AP ["Winterfall Firewater"] = "Interface\\Icons\\INV_Potion_92", ["Juju Might"] = "Interface\\Icons\\INV_Misc_MonsterScales_07", -- STR (aura-name variants share the item icon, as for Mageblood below) ["Juju Power"] = "Interface\\Icons\\INV_Misc_MonsterScales_11", ["Elixir of Giants"] = "Interface\\Icons\\INV_Potion_61", ["Elixir of the Giants"] = "Interface\\Icons\\INV_Potion_61", ["Strike of the Scorpok"] = "Interface\\Icons\\INV_Misc_MonsterScales_09", ["Rage of Ages"] = "Interface\\Icons\\INV_Scroll_03", -- AGI ["Elixir of the Mongoose"] = "Interface\\Icons\\INV_Potion_32", ["Elixir of Greater Agility"] = "Interface\\Icons\\INV_Potion_94", ["Greater Agility"] = "Interface\\Icons\\INV_Potion_94", -- SP ["Greater Arcane Elixir"] = "Interface\\Icons\\INV_Potion_25", ["Arcane Elixir"] = "Interface\\Icons\\INV_Potion_30", ["Elixir of Shadow Power"] = "Interface\\Icons\\INV_Potion_46", ["Greater Frost Power"] = "Interface\\Icons\\INV_Potion_13", -- MP5 (both TWoW name variants share the item icon) ["Mageblood Potion"] = "Interface\\Icons\\INV_Potion_45", ["Mageblood"] = "Interface\\Icons\\INV_Potion_45", -- ARM ["Elixir of Superior Defense"] = "Interface\\Icons\\INV_Potion_66", ["Elixir of Greater Defense"] = "Interface\\Icons\\INV_Potion_43", -- HP ["Elixir of Fortitude"] = "Interface\\Icons\\INV_Potion_44", ["Health II"] = "Interface\\Icons\\INV_Potion_44", ["Rumsey Rum Black Label"] = "Interface\\Icons\\INV_Drink_04", ["Rumsey Rum"] = "Interface\\Icons\\INV_Drink_08", -- FR ["Greater Fire Protection"] = "Interface\\Icons\\INV_Potion_24", ["Fire Protection"] = "Interface\\Icons\\INV_Potion_16", -- class buffs ["Arcane Intellect"] = "Interface\\Icons\\Spell_Holy_MagicalSentry", ["Arcane Brilliance"] = "Interface\\Icons\\Spell_Holy_ArcaneIntellect", ["Mark of the Wild"] = "Interface\\Icons\\Spell_Nature_Regeneration", ["Gift of the Wild"] = "Interface\\Icons\\Spell_Nature_GiftoftheWild", ["Power Word: Fortitude"] = "Interface\\Icons\\Spell_Holy_WordFortitude", ["Prayer of Fortitude"] = "Interface\\Icons\\Spell_Holy_PrayerOfFortitude", ["Shadow Protection"] = "Interface\\Icons\\Spell_Shadow_AntiShadow", ["Soulstone Resurrection"] = "Interface\\Icons\\Spell_Shadow_SoulGem", } -- Debuff cosmetics. Resurrection Sickness and Weakened Soul genuinely -- share one icon on 1.12 (both use -- Spell_Holy_AshesToAshes -- realism over showcase variety). local DEBUFF_ICONS = { ["Resurrection Sickness"] = "Interface\\Icons\\Spell_Holy_AshesToAshes", ["Curse of Recklessness"] = "Interface\\Icons\\Spell_Shadow_UnholyStrength", ["Weakened Soul"] = "Interface\\Icons\\Spell_Holy_AshesToAshes", } -- debuff literal builder: names + ids given, textures from DEBUFF_ICONS -- (fresh tables per call -- store builds must never share references) local function debuffsOf(namesList, idsList) local d = { names = {}, ids = {}, textures = {} } for i = 1, table.getn(namesList) do local nm = namesList[i] d.names[nm] = true if DEBUFF_ICONS[nm] then d.textures[nm] = DEBUFF_ICONS[nm] end end for i = 1, table.getn(idsList) do d.ids[idsList[i]] = true end return d end -- auras -> textures table (store shape: auras.textures[name] = icon path). -- Names are taken directly; ids are resolved against the REAL data tables -- (every table name that carries one of the player's ids gets its icon), -- so whatever alias DC_Model's id->name index picks, the texture is there. local function texturesFor(auras) local t = {} for name in pairs(auras.names) do if ICONS[name] then t[name] = ICONS[name] end end if DC.CONSUMABLES then for name, def in pairs(DC.CONSUMABLES) do if def.spellID and def.spellID > 0 and auras.ids[def.spellID] and ICONS[name] then t[name] = ICONS[name] end end end if DC.CLASSBUFFS then for name, def in pairs(DC.CLASSBUFFS) do if def.ids then for i = 1, table.getn(def.ids) do if auras.ids[def.ids[i]] and ICONS[name] then t[name] = ICONS[name] end end end end end return t end -- Main-hand item subtype per class for the hit filler pass below (the -- weapon skill the melee-hit cap is derived from). Plausible vanilla -- choices, nothing more -- the showcase players carry explicit literals. local HIT_MAIN_SUB = { WARRIOR = "One-Handed Swords", PALADIN = "Two-Handed Maces", HUNTER = "Polearms", ROGUE = "Daggers", PRIEST = "Staves", SHAMAN = "One-Handed Maces", MAGE = "Staves", WARLOCK = "Staves", DRUID = "Staves", } -- All 16 equipment slots the scan reads (11 enchant + neck + 2 rings + -- 2 trinket invSlots). local INV_SLOTS = { 1, 2, 3, 15, 5, 9, 10, 7, 8, 16, 17, 18, 11, 12, 13, 14 } -- Per-slot base item cosmetics: EVERY gear entry table carries texture, -- name and quality (the UI renders item icons). local SLOT_BASE = { [1] = { name = "Simulated Faceguard", quality = 3, texture = "Interface\\Icons\\INV_Helmet_09" }, [2] = { name = "Simulated Choker", quality = 3, texture = "Interface\\Icons\\INV_Jewelry_Necklace_07" }, [3] = { name = "Simulated Spaulders", quality = 3, texture = "Interface\\Icons\\INV_Shoulder_01" }, [15] = { name = "Simulated Drape", quality = 3, texture = "Interface\\Icons\\INV_Misc_Cape_16" }, [5] = { name = "Simulated Breastplate", quality = 4, texture = "Interface\\Icons\\INV_Chest_Plate04" }, [9] = { name = "Simulated Bracers", quality = 3, texture = "Interface\\Icons\\INV_Bracer_13" }, [10] = { name = "Simulated Gauntlets", quality = 3, texture = "Interface\\Icons\\INV_Gauntlets_28" }, [7] = { name = "Simulated Legplates", quality = 4, texture = "Interface\\Icons\\INV_Pants_04" }, [8] = { name = "Simulated Sabatons", quality = 3, texture = "Interface\\Icons\\INV_Boots_Plate_06" }, [16] = { name = "Simulated Edge", quality = 4, texture = "Interface\\Icons\\INV_Sword_39" }, [17] = { name = "Simulated Barrier", quality = 3, texture = "Interface\\Icons\\INV_Shield_06" }, [18] = { name = "Simulated Repeater", quality = 2, texture = "Interface\\Icons\\INV_Weapon_Crossbow_02" }, [11] = { name = "Simulated Signet", quality = 3, texture = "Interface\\Icons\\INV_Jewelry_Ring_21" }, [12] = { name = "Simulated Band", quality = 2, texture = "Interface\\Icons\\INV_Jewelry_Ring_14" }, [13] = { name = "Simulated Charm", quality = 2, texture = "Interface\\Icons\\INV_Misc_OrnateBox" }, [14] = { name = "Simulated Insignia", quality = 3, texture = "Interface\\Icons\\INV_Jewelry_Talisman_07" }, } -- Base gear: every slot filled and enchanted (enchant 900+invSlot except -- neck/rings/trinkets), every entry with name/quality/texture from -- SLOT_BASE. Table overrides are MERGED with the slot cosmetics (missing -- name/quality/texture filled in from SLOT_BASE, everything else -- id, -- enchant, subType, own cosmetics -- kept); "EMPTY" stays "EMPTY". -- Neck/rings (2/11/12) default to enchant=0 like trinkets -- but unlike -- trinkets they ARE enchantable on this server (data/enchants.lua, kind -- "ench"), so Simtank's override below demonstrates a real neck enchant -- (their expectation defaults to OFF, so by default this renders the new -- "not expected, item still shown" tile -- see core/model.lua). local function gearWith(over) local g = {} for i = 1, table.getn(INV_SLOTS) do local inv = INV_SLOTS[i] local base = SLOT_BASE[inv] local e = { id = 5000 + inv, enchant = 900 + inv, quality = base.quality, name = base.name, texture = base.texture } if inv == 2 or inv == 11 or inv == 12 or inv == 13 or inv == 14 then e.enchant = 0 -- neck/rings/trinkets: enchant-free baseline end g[inv] = e end if over then for inv, v in pairs(over) do if type(v) == "table" then local base = SLOT_BASE[inv] if v.name == nil then v.name = base.name end if v.quality == nil then v.quality = base.quality end if v.texture == nil then v.texture = base.texture end end g[inv] = v end end return g end -- ------------------------------------------------------------------ -- The roster. Deterministic literals only. -- ------------------------------------------------------------------ function DC_Sim.buildStore() -- deterministic clock (see header): base for scanAt -- and the last-known At offsets below local now = 1000 if type(GetTime) == "function" then now = GetTime() end -- reset transient sim confirmations to the built-in set (fresh copy) DC.simRoles = {} for name, role in pairs(DC_Sim.ROLES) do DC.simRoles[name] = role end local players = {} local function add(p) table.insert(players, p) end -- ================= TANKS (3) -- all readable, 0 gaps in every tab ==== -- Simtank: spell-ID-only auras (names empty) -> proves the ID match -- path alone. Thrown weapon -> ranged enchant NOTEXP. Epic + rare -- trinkets (WoW quality: 4 = epic, 3 = rare). Neck enchant demo (D6): -- a real neck enchant>0 -- NECK's expectation defaults to OFF, so this -- shows the "not expected, item still shown, bright, no marker" tile -- by default; enabling the checkbox for TANK/WARRIOR would flip it to -- HAS with a green corner marker (same data, no sim change needed). -- Fire resistance 315 -- highest of the three tanks (showcase spread). add{ name = "Simtank", class = "WARRIOR", online = true, guid = "0xF530000000000001", subgroup = 1, aurasRead = true, auras = { names = {}, ids = set({ 17626, 15852, 16323, 17538, 11348, 3593, 17543, 9885, 10938, 10958 }) }, -- FLASK Titans, FOOD Chili, STR JujuPower, AGI Mongoose, ARM SupDef, -- HP Fortitude, FR GFPP, MOTW r7, PWF r6, SPROT r3 weaponMain = true, weaponMainName = "Dense Sharpening Stone", gearRead = true, resistsRead = true, resists = { [2] = 315 }, -- hit: the MEASURED-skill showcase (what the own character looks -- like) -- skillExact means the 305 came from the skill window, -- which lowers the yellow cap to 7.0; melee 8 sits above it. -- talentsRead with NO talent numbers is its own state and a real -- one: a protection warrior has no hit talent at all, so this is -- a VERIFIED zero rather than the "not received yet" case below. race = "Human", hit = { melee = 8, spell = 0, schools = {}, hitRead = true, mainSub = "One-Handed Maces", offSub = "Shields", rangedSub = "Thrown", skillName = "Maces", skill = 305, skillExact = true, talentsRead = true }, gear = gearWith({ [17] = { id = 5117, enchant = 917, subType = "Shields", quality = 3 }, [18] = { id = 5118, enchant = 0, subType = "Thrown", name = "Simulated Throwing Axe" }, [2] = { id = 5299, enchant = 950, name = "Simulated Neck Chain" }, [13] = { id = 5213, enchant = 0, quality = 4, name = "Simulated Drake Talisman", texture = "Interface\\Icons\\INV_Misc_Bone_10" }, [14] = { id = 5214, enchant = 0, quality = 3, name = "Simulated Hand of Justice", texture = "Interface\\Icons\\INV_Jewelry_Talisman_01" }, }) } -- Simwall: name-only auras (ids empty = sparse) -> proves the name -- fallback path alone. Gun with scope (enchant>0) -> ranged HAS. -- Deliberately weaponMain=true WITHOUT weaponMainName (so -- the generic "main hand temp enchant" detail path stays exercised). -- Fire resistance 240. add{ name = "Simwall", class = "WARRIOR", online = true, guid = "0xF530000000000002", subgroup = 1, aurasRead = true, auras = { names = set({ "Flask of the Titans", "Well Fed", "Juju Power", "Elixir of the Mongoose", "Elixir of Superior Defense", "Elixir of Fortitude", "Greater Fire Protection", "Mark of the Wild", "Power Word: Fortitude", "Shadow Protection" }), ids = {} }, weaponMain = true, gearRead = true, resistsRead = true, resists = { [2] = 240 }, -- hit: the BELOW-cap showcase -- a human swordsman, so the racial -- specialization is ESTIMATED into the skill (300 + 5 => cap 7.0) -- and 6 still falls short of it race = "Human", hit = { melee = 6, spell = 0, schools = {}, hitRead = true, talentsRead = false, mainSub = "One-Handed Swords", offSub = "Shields", rangedSub = "Guns" }, gear = gearWith({ [17] = { id = 5127, enchant = 917, subType = "Shields", quality = 3 }, [18] = { id = 5128, enchant = 33, subType = "Guns", name = "Simulated Blunderbuss" }, }) } -- Simsteel: mixed matching -- ids for everything with a known spell ID, -- "Well Fed" (id-less entry) by name. NOTE the model's strict rule: once -- a player has ANY ids, names of id-bearing entries no longer count, so -- a "mixed" player may only name-match id-less entries. Bow with scope. -- Fire resistance 180 -- lowest of the three tanks. add{ name = "Simsteel", class = "WARRIOR", online = true, guid = "0xF530000000000003", subgroup = 1, aurasRead = true, auras = { names = set({ "Well Fed" }), ids = set({ 17629, 11405, 11334, 11349, 25804, 7233, 21849, 21562, 10957 }) }, -- FLASK ChromRes, STR Giants, AGI GreaterAgi, ARM GreaterDef, -- HP RumseyBlack, FR FireProt, MOTW Gift, PWF Prayer, SPROT r2 weaponMain = true, gearRead = true, resistsRead = true, resists = { [2] = 180 }, -- hit: the BOOK showcase -- an orc axeman (racial +5 => cap 7.0); -- ticking "Axes" for this player in the options adds another +3 -- skill and drops the cap to 6.4 on the very next refresh (the -- book flag lives in the DB, never in the store) race = "Orc", hit = { melee = 8, spell = 0, schools = {}, hitRead = true, talentsRead = false, mainSub = "One-Handed Axes", offSub = "Shields", rangedSub = "Bows" }, gear = gearWith({ [17] = { id = 5137, enchant = 917, subType = "Shields", quality = 3 }, [18] = { id = 5138, enchant = 33, subType = "Bows", name = "Simulated Longbow" }, }) } -- ================= MELEE (6) ========================================= -- Simfury: UNCONFIRMED -> heuristic suggests MELEE ("class default"; -- no shield). EMPTY off hand (fury with a 2H habit) -> NOTEXP under the -- model's exemption resolution (no item on an exemptable slot = nothing -- to enchant). Missing HP + FR consumables, missing PWF + SPROT class -- buffs, head enchant missing -> whisper-eligible in every tab. add{ name = "Simfury", class = "WARRIOR", online = true, guid = "0xF530000000000004", subgroup = 1, aurasRead = true, auras = { names = set({ "Well Fed", "Juju Might", "Juju Power", "Elixir of the Mongoose", "Mark of the Wild" }), ids = {} }, weaponMain = true, gearRead = true, gear = gearWith({ [1] = { id = 5141, enchant = 0 }, -- head enchant missing [17] = "EMPTY", -- empty off hand -> gap [18] = { id = 5148, enchant = 0, subType = "Thrown" }, }) } -- Simrogue: ID matches for everything id-bearing, "Well Fed" by name. -- EMPTY trinket slot 14 (the one empty trinket of the roster). -- FR + SPROT missing. Afflicted 1/3: Resurrection Sickness (shared -- with Simtree -> RS afflicted=2 = first debuff column). add{ name = "Simrogue", class = "ROGUE", online = true, guid = "0xF530000000000005", subgroup = 2, aurasRead = true, auras = { names = set({ "Well Fed" }), ids = set({ 16329, 17538, 16323, 3593, 9885, 10938 }) }, -- AP JujuMight, AGI Mongoose, STR JujuPower, HP Fortitude, -- MOTW r7, PWF r6 debuffs = debuffsOf({ "Resurrection Sickness" }, { 15007 }), weaponMain = true, gearRead = true, -- hit: the DUAL-WIELD showcase -- two weapon subtypes, so the cell -- carries the yellow cap (gnome dagger racial => 7.0, met by 9) -- AND the white dual-wield cap (26.0) in its tooltip. -- Also the TALENT showcase on the melee side: the 9 is 4 from -- gear plus 5 from the rogue's 5/5 melee-hit talent, and the -- tooltip names both parts instead of one lump sum. race = "Gnome", hit = { melee = 9, gearMelee = 4, talentMelee = 5, talentsRead = true, -- measured skill goes WITH the read talents: both come -- from the own character in the live client, so a -- talent-readable sim player carries a measured skill -- too (305 is what the gnome dagger racial estimates -- anyway, so the cap is the same either way) skill = 305, skillExact = true, skillName = "Daggers", spell = 0, schools = {}, hitRead = true, mainSub = "Daggers", offSub = "One-Handed Swords", rangedSub = "Thrown" }, gear = gearWith({ [18] = { id = 5158, enchant = 0, subType = "Thrown" }, [12] = "EMPTY", -- empty ring 2 -> gap [14] = "EMPTY", -- empty trinket -> gap }) } -- Simblade: OFFLINE (auras/gear nil, weaponMain nil, no distance -- -- a disconnected unit cannot be measured). Carries the -- runtime cache's LAST-KNOWN snapshot from before the disconnect -- -- lastAuras (HP/FR consumables + SPROT class buff absent => dimmed -- MISSING reachable, the rest dimmed HAS) and lastGear (wrist -- enchant 0 => dimmed MISSING; EMPTY off hand => cached classify -- NOTEXP => NO cell.last, the plain "?" stays). No lastWeaponMain: -- the WPN cell shows what a channel WITHOUT cache looks like. -- IRON RULE: display-only -- Simblade stays UNKNOWN in every count. local bladeAuras = { names = set({ "Well Fed", "Juju Might", "Juju Power", "Elixir of the Mongoose", "Mark of the Wild", "Power Word: Fortitude" }), ids = {} } bladeAuras.textures = texturesFor(bladeAuras) add{ name = "Simblade", class = "ROGUE", online = false, guid = "0xF530000000000006", subgroup = 8, aurasRead = false, gearRead = false, unreadableReason = "offline", lastAuras = bladeAuras, lastAurasAt = now - 540, lastGear = gearWith({ [9] = { id = 5162, enchant = 0 }, -- wrist: dimmed MISSING [17] = "EMPTY", -- cached NOTEXP -> no .last [18] = { id = 5163, enchant = 0, subType = "Thrown" }, }), lastGearAt = now - 540 } -- Simstab: name matching, only gap on consumables = no temp weapon -- enchant; class buffs complete; two enchant gaps. add{ name = "Simstab", class = "ROGUE", online = true, guid = "0xF530000000000007", subgroup = 2, aurasRead = true, auras = { names = set({ "Well Fed", "Juju Might", "Juju Power", "Elixir of Greater Agility", "Elixir of Fortitude", "Fire Protection", "Mark of the Wild", "Power Word: Fortitude", "Shadow Protection" }), ids = {} }, weaponMain = false, gearRead = true, gear = gearWith({ [9] = { id = 5169, enchant = 0 }, -- wrist enchant missing [7] = { id = 5167, enchant = 0 }, -- legs enchant missing [18] = { id = 5168, enchant = 0, subType = "Thrown" }, }) } -- Simkitty: druid in Cat Form (confirmed MELEE anyway). AP/AGI/MOTW via -- spell ID, FOOD + form by name; STR/HP/FR/WPN missing; druid: EMPTY -- ranged + EMPTY off hand (staff) -> NOTEXP (exemptable slots). add{ name = "Simkitty", class = "DRUID", online = true, guid = "0xF530000000000008", subgroup = 2, aurasRead = true, auras = { names = set({ "Cat Form", "Well Fed" }), ids = set({ 16329, 17538, 9885 }) }, weaponMain = false, gearRead = true, gear = gearWith({ [17] = "EMPTY", [18] = "EMPTY", }) } -- Simsham: enhancement -- CONFIRMED MELEE although the heuristic would -- say HEALER. Full consumables/class buffs (ids + "Well Fed" by name); -- shaman: EMPTY ranged slot -> NOTEXP. -- Note (roles stay as-is, no -- shipped default changes): a shaman is also the per-class -- expectation poster child -- a TANK-confirmed shaman would get -- expectations.TANK.SHAMAN.AI = true (INT for the mana-fed tank kit) -- flipped by the user in the options grid; AI ships as the CASTER/HEALER -- role default for every performing class. add{ name = "Simsham", class = "SHAMAN", online = true, guid = "0xF530000000000009", subgroup = 2, aurasRead = true, auras = { names = set({ "Well Fed" }), ids = set({ 17038, 11405, 17538, 25804, 17543, 9885, 10938, 10958 }) }, -- AP Firewater, STR Giants, AGI Mongoose, HP RumseyBlack, FR GFPP, -- MOTW r7, PWF r6, SPROT r3 weaponMain = true, gearRead = true, gear = gearWith({ [17] = { id = 5197, enchant = 917, subType = "Shields", quality = 3 }, [18] = "EMPTY", }) } -- ================= RANGED (3) ======================================== -- Simhuntara: AP/AGI/MOTW via spell ID; HP/FR + PWF/SPROT missing. -- Far case 1/2: readable at distance 55 (>= FAR_LIMIT 40). -- THE cross-continent case -- the aura paths reach -- anywhere, gear inspection does not: gearRead=false + gear nil on an -- ONLINE, aura-readable player, with the runtime cache's lastGear -- attached (a 20-minutes-old snapshot). EQUIPMENT renders the -- "out of range" row note + dimmed tiles; nothing counts (IRON RULE). -- Nature resistance 120 -- resist reads are their OWN channel, -- independent of gearRead=false above (a resistance read is neither -- an aura nor a gear read). add{ name = "Simhuntara", class = "HUNTER", online = true, guid = "0xF53000000000000A", subgroup = 4, distance = 55, aurasRead = true, auras = { names = set({ "Well Fed" }), ids = set({ 16329, 17538, 9885 }) }, weaponMain = true, gearRead = false, resistsRead = true, resists = { [3] = 120 }, -- hit: THE unreadable showcase -- hit is summed from item -- tooltips, so it dies with the same range limit as the gear read -- (resistances survive it, they are their own channel) hit = { hitRead = false }, lastGear = gearWith({ [18] = { id = 5178, enchant = 33, subType = "Guns", name = "Simulated Rifle" }, }), lastGearAt = now - 1200 } -- Simbow: fully clean row (green contrast inside a non-green group). add{ name = "Simbow", class = "HUNTER", online = true, guid = "0xF53000000000000B", subgroup = 3, distance = 31, aurasRead = true, auras = { names = set({ "Well Fed", "Juju Might", "Elixir of Greater Agility", "Rumsey Rum", "Greater Fire Protection", "Mark of the Wild", "Power Word: Fortitude", "Shadow Protection" }), ids = {} }, weaponMain = true, gearRead = true, -- hit: the RANGED showcase -- a real bow, so the ranged column is -- a question at all; 8 meets its 8.0 cap (a dwarf's racial is on -- guns, not bows, so nothing lowers this one) race = "Dwarf", hit = { melee = 8, spell = 0, schools = {}, hitRead = true, talentsRead = false, mainSub = "Polearms", rangedSub = "Bows" }, gear = gearWith({ [18] = { id = 5188, enchant = 33, subType = "Bows", name = "Simulated Warbow" }, }) } -- Simtrue: READ FAILED (online, but the aura path returned zero auras). -- distance recorded as diagnosis, never as filter. add{ name = "Simtrue", class = "HUNTER", online = true, guid = "0xF53000000000000C", subgroup = 8, distance = 203, aurasRead = false, gearRead = false, unreadableReason = "read failed" } -- ================= CASTERS (7) ======================================= -- Simmage: everything id-bearing via spell ID, FOOD by name; FR -- missing. Wand -> ranged NOTEXP, holdable frill -> off hand NOTEXP. -- Frost 85 + arcane 45 -- the two-school showcase (every other -- resist-carrying player below has only one school set explicitly). add{ name = "Simmage", class = "MAGE", online = true, guid = "0xF53000000000000D", subgroup = 4, aurasRead = true, auras = { names = set({ "Well Fed" }), ids = set({ 17628, 23028, 17539, 24363, 9885, 10938, 10958 }) }, -- FLASK Supreme, AI Brilliance, SP GAE, MP5 Mageblood, MOTW, PWF, SPROT weaponMain = true, weaponMainName = "Brilliant Wizard Oil", gearRead = true, resistsRead = true, resists = { [4] = 85, [6] = 45 }, -- hit: THE per-school showcase and the reason the tab has eight -- columns. 10% generic spell hit from gear, and a frost mage's -- two school talents on top: Arcane Focus 5/5 (+10 arcane) and -- Elemental Precision 3/3 (+6 frost AND fire). Her columns -- therefore read arcane 20.0, fire 16.0, frost 16.0, everything -- else 10.0 -- three different numbers on ONE caster, which no -- single spell-hit column could ever show. The wand makes the -- ranged column NOTEXP and the orb in the off hand is not a -- weapon, so no dual-wield note either. race = "Gnome", hit = { melee = 0, spell = 10, gearSpell = 10, schools = {}, talentsRead = true, talentSpell = 0, talentSchools = { arcane = 10, fire = 6, frost = 6 }, -- measured skill rides along with the read talents (see -- Simrogue): a staff has no racial, so 300 is also what -- the estimate would have produced skill = 300, skillExact = true, skillName = "Staves", hitRead = true, mainSub = "Staves", offSub = "Miscellaneous", rangedSub = "Wands" }, gear = gearWith({ [17] = { id = 5207, enchant = 0, subType = "Miscellaneous", name = "Simulated Orb" }, [18] = { id = 5208, enchant = 0, subType = "Wands", name = "Simulated Wand" }, }) } -- Simfrost: name-only, several gaps (MP5/FR/WPN missing; MOTW/PWF/SPROT -- missing; head+shoulder enchants missing; EMPTY ranged on a mage -- -- forgot the wand). Afflicted 2/3: Curse of Recklessness + Weakened -- Soul (TWO debuff columns from one player). Far case 2/2: readable -- at distance 120. add{ name = "Simfrost", class = "MAGE", online = true, guid = "0xF53000000000000E", subgroup = 4, distance = 120, aurasRead = true, auras = { names = set({ "Flask of Supreme Power", "Well Fed", "Arcane Elixir", "Arcane Intellect" }), ids = {} }, debuffs = debuffsOf({ "Curse of Recklessness", "Weakened Soul" }, { 11717, 6788 }), weaponMain = false, gearRead = true, -- hit: BELOW the spell cap (8 of 16) and an EMPTY ranged slot -- -- the second NOTEXP path (no ranged item at all, not just a wand) race = "Undead", hit = { melee = 0, spell = 8, schools = {}, hitRead = true, talentsRead = false, mainSub = "Staves" }, gear = gearWith({ [1] = { id = 5211, enchant = 0 }, [3] = { id = 5212, enchant = 0 }, [18] = "EMPTY", }) } -- Simzap: OFFLINE. add{ name = "Simzap", class = "MAGE", online = false, guid = "0xF53000000000000F", subgroup = 8, aurasRead = false, gearRead = false, unreadableReason = "offline" } -- Simlock: THE wand warlock (ranged enchant NOTEXP via subtype). -- Name-only reader (ids empty) so the "Mageblood" TWoW alias entry is -- exercised via the name fallback. One gap: SPROT. add{ name = "Simlock", class = "WARLOCK", online = true, guid = "0xF530000000000010", subgroup = 5, aurasRead = true, auras = { names = set({ "Well Fed", "Flask of Supreme Power", "Greater Arcane Elixir", "Mageblood", "Fire Protection", "Arcane Intellect", "Mark of the Wild", "Power Word: Fortitude" }), ids = {} }, weaponMain = true, gearRead = true, -- hit: the school-specific GEAR showcase, and the counterpart to -- Simmage's talents -- an item line worth +3% shadow spell hit -- lifts his shadow column to 9.0 while every other school reads -- 6.0. talentsRead stays false: this fixture models a player whose -- inspect reply (scan/talents.lua) has not arrived yet, so the -- number is honestly -- gear-only and every tooltip on this row says so (a warlock -- would realistically add shadow talents on top of this). hit = { melee = 0, spell = 6, gearSpell = 6, schools = { Shadow = 3 }, talentsRead = false, hitRead = true, mainSub = "Staves", rangedSub = "Wands" }, gear = gearWith({ [18] = { id = 5218, enchant = 0, subType = "Wands", name = "Simulated Ghastly Wand" }, }) } -- Simafflic: many consumable gaps; Soulstone on a CASTER -> SOUL cell -- is NOTEXP although the aura is present (expectation wins). add{ name = "Simafflic", class = "WARLOCK", online = true, guid = "0xF530000000000011", subgroup = 5, aurasRead = true, auras = { names = set({ "Well Fed", "Soulstone Resurrection" }), ids = {} }, weaponMain = false, gearRead = true, gear = gearWith({ [8] = { id = 5228, enchant = 0 }, -- feet enchant missing [15] = { id = 5225, enchant = 0 }, -- back enchant missing [18] = { id = 5229, enchant = 0, subType = "Wands" }, }) } -- Simshade: UNCONFIRMED shadow priest -> heuristic suggests CASTER -- ("Shadowform at scan time"). Otherwise a completely clean row: -- a suggestion badge is not a gap. add{ name = "Simshade", class = "PRIEST", online = true, guid = "0xF530000000000012", subgroup = 5, aurasRead = true, auras = { names = set({ "Shadowform", "Flask of Supreme Power", "Well Fed", "Elixir of Shadow Power", "Mageblood Potion", "Greater Fire Protection", "Arcane Intellect", "Mark of the Wild", "Power Word: Fortitude", "Shadow Protection" }), ids = {} }, weaponMain = true, gearRead = true, gear = gearWith({ [18] = { id = 5238, enchant = 0, subType = "Wands" }, }) } -- Simboom: moonkin (confirmed CASTER). FLASK/MP5/FR/WPN missing, -- MOTW/PWF/SPROT missing, wrist enchant missing, EMPTY ranged. add{ name = "Simboom", class = "DRUID", online = true, guid = "0xF530000000000013", subgroup = 6, aurasRead = true, auras = { names = set({ "Moonkin Form", "Well Fed", "Greater Arcane Elixir", "Arcane Intellect" }), ids = {} }, weaponMain = false, gearRead = true, gear = gearWith({ [9] = { id = 5249, enchant = 0 }, [18] = "EMPTY", }) } -- ================= HEALERS (6) ======================================= -- Simheala: complete healer, all id-bearing buffs via spell ID (epic -- trinket, holdable off hand -> NOTEXP). Clean row. add{ name = "Simheala", class = "PRIEST", online = true, guid = "0xF530000000000014", subgroup = 6, aurasRead = true, auras = { names = set({ "Well Fed" }), ids = set({ 17627, 20765, 24363, 3593, 7233, 10157, 9885, 10938, 10958 }) }, -- FLASK Wisdom, SOUL r5, MP5 Mageblood, HP Fortitude, FR FireProt, -- AI r5, MOTW r7, PWF r6, SPROT r3 weaponMain = true, gearRead = true, gear = gearWith({ [17] = { id = 5257, enchant = 0, subType = "Miscellaneous", name = "Simulated Prayer Book" }, [18] = { id = 5258, enchant = 0, subType = "Wands" }, [13] = { id = 5263, enchant = 0, quality = 4, name = "Simulated Eye of the Naga", texture = "Interface\\Icons\\INV_Jewelry_Talisman_06" }, }) } -- Simlight: READ FAILED, distance 87. add{ name = "Simlight", class = "PRIEST", online = true, guid = "0xF530000000000015", subgroup = 8, distance = 87, aurasRead = false, gearRead = false, unreadableReason = "read failed" } -- Simtree: HP/FR/WPN missing, SOUL missing, hands enchant missing, -- EMPTY ranged. "Mageblood" alias again on the MP5 slot. Afflicted -- 3/3: shares Resurrection Sickness with Simrogue (RS afflicted=2). add{ name = "Simtree", class = "DRUID", online = true, guid = "0xF530000000000016", subgroup = 6, aurasRead = true, auras = { names = set({ "Flask of Distilled Wisdom", "Well Fed", "Mageblood", "Arcane Intellect", "Mark of the Wild", "Power Word: Fortitude", "Shadow Protection" }), ids = {} }, debuffs = debuffsOf({ "Resurrection Sickness" }, { 15007 }), weaponMain = false, gearRead = true, gear = gearWith({ [10] = { id = 5270, enchant = 0 }, [18] = "EMPTY", }) } -- Simchain: resto shaman, SPROT + SOUL missing, EMPTY ranged -> NOTEXP. add{ name = "Simchain", class = "SHAMAN", online = true, guid = "0xF530000000000017", subgroup = 7, aurasRead = true, auras = { names = set({ "Well Fed" }), ids = set({ 17627, 24363, 25804, 7233, 10156, 9885, 10938 }) }, -- FLASK Wisdom, MP5 Mageblood, HP RumseyBlack, FR FireProt, -- AI r4, MOTW r7, PWF r6 weaponMain = true, gearRead = true, gear = gearWith({ [17] = { id = 5277, enchant = 917, subType = "Shields", quality = 3 }, [18] = "EMPTY", }) } -- Simholy: name-only healer, complete on auras incl. SOUL; chest -- enchant missing, EMPTY ranged (paladin), holdable off hand NOTEXP. add{ name = "Simholy", class = "PALADIN", online = true, guid = "0xF530000000000018", subgroup = 7, aurasRead = true, auras = { names = set({ "Flask of Distilled Wisdom", "Well Fed", "Mageblood Potion", "Elixir of Fortitude", "Greater Fire Protection", "Arcane Intellect", "Mark of the Wild", "Power Word: Fortitude", "Shadow Protection", "Soulstone Resurrection" }), ids = {} }, weaponMain = true, gearRead = true, gear = gearWith({ [5] = { id = 5285, enchant = 0 }, [17] = { id = 5287, enchant = 0, subType = "Miscellaneous", name = "Simulated Libram of Hope" }, [18] = "EMPTY", }) } -- Simglow: HP via the id-less "Rumsey Rum" name entry (spellID 0) WHILE -- ids are present -- exercises exactly the model rule that id-less -- names still count for players with ids. SPROT missing, EMPTY ranged. add{ name = "Simglow", class = "PALADIN", online = true, guid = "0xF530000000000019", subgroup = 7, aurasRead = true, auras = { names = set({ "Well Fed", "Rumsey Rum" }), ids = set({ 17627, 21850, 21564, 24363, 7233, 1461, 20762 }) }, -- FLASK Wisdom, MOTW Gift, PWF Prayer, MP5 Mageblood, FR FireProt, -- AI r3, SOUL r2 weaponMain = true, gearRead = true, gear = gearWith({ [17] = { id = 5297, enchant = 917, subType = "Shields", quality = 3 }, [18] = "EMPTY", }) } -- ================= FULL-RAID FILL-IN (25 -> 40, see header) ========= -- Every player below is deliberately "clean": cleanAuras() covers -- every consumable/class-buff slot any role could expect, default -- gearWith() leaves every enchant slot filled and both trinkets -- present, weaponMain=true carries no name (the generic WPN detail -- path, like Simwall -- keeps the "exactly two named enchants" count -- unchanged). Resist/hit/debuffs come from the index-based filler -- passes below, exactly like every other filler player in the -- original 25 -- their numbers are deliberately unpinned. -- Simblock: the 4th tank (protection paladin) -- keeps the TANK group -- entirely readable with 0 gaps (the green-pill case stays a green -- pill at 4 members). RNGD is class-exempt for paladins either way. add{ name = "Simblock", class = "PALADIN", online = true, guid = "0xF53000000000001A", subgroup = 1, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } add{ name = "Simgrit", class = "WARRIOR", online = true, guid = "0xF53000000000001B", subgroup = 2, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } add{ name = "Simslash", class = "ROGUE", online = true, guid = "0xF53000000000001C", subgroup = 3, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } -- Simox: melee-spec hunter (a real TWoW spec, see DC_Roles.CLASS_ROLES) -- and the column-width showcase: the shortest name in the roster. add{ name = "Simox", class = "HUNTER", online = true, guid = "0xF53000000000001D", subgroup = 3, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } add{ name = "Simjolt", class = "SHAMAN", online = true, guid = "0xF53000000000001E", subgroup = 3, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } add{ name = "Simzeal", class = "PALADIN", online = true, guid = "0xF53000000000001F", subgroup = 3, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } -- Simarrow/Simscout: real ranged weapons (unlike the other fill-in -- players' default ranged slot) so the RANGED group's HIT tab is not -- entirely NOTEXP -- the hit filler pass below reads gear[18].subType. add{ name = "Simarrow", class = "HUNTER", online = true, guid = "0xF530000000000020", subgroup = 4, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith({ [18] = { id = 5426, enchant = 33, subType = "Bows", name = "Simulated Huntsbow" }, }) } add{ name = "Simscout", class = "HUNTER", online = true, guid = "0xF530000000000021", subgroup = 4, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith({ [18] = { id = 5427, enchant = 33, subType = "Guns", name = "Simulated Scattergun" }, }) } -- Simveil: the 3rd offline case (with Simblade/Simzap: 3 offline + 2 -- read-failed = 5 of 40 unreadable). Deliberately in subgroup 8 with -- the other 4 unreadable players -- a raid subgroup that is entirely -- unreadable, the stress case from the header. add{ name = "Simveil", class = "HUNTER", online = false, guid = "0xF530000000000022", subgroup = 8, aurasRead = false, gearRead = false, unreadableReason = "offline" } add{ name = "Simember", class = "MAGE", online = true, guid = "0xF530000000000023", subgroup = 5, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } add{ name = "Simdread", class = "WARLOCK", online = true, guid = "0xF530000000000024", subgroup = 5, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } add{ name = "Simvoid", class = "PRIEST", online = true, guid = "0xF530000000000025", subgroup = 6, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } -- Simthornweaver: moonkin druid and one of the two long-name -- column-width showcases (14 characters). add{ name = "Simthornweaver", class = "DRUID", online = true, guid = "0xF530000000000026", subgroup = 6, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } -- Simshadowmender: resto shaman and the other long-name showcase -- (15 characters, the longest name in the roster). add{ name = "Simshadowmender", class = "SHAMAN", online = true, guid = "0xF530000000000027", subgroup = 7, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } add{ name = "Simbloom", class = "DRUID", online = true, guid = "0xF530000000000028", subgroup = 7, aurasRead = true, auras = cleanAuras(), weaponMain = true, gearRead = true, gear = gearWith() } -- Icon coverage pass: give every readable player -- a textures table covering all of their HAS-producing buff names -- -- derived programmatically from names + ids, so both model match paths -- deliver an icon. Deterministic (pure function of the literals above). -- Debuff pass: every READABLE player without a debuff -- literal gets the EMPTY debuffs shape -- zero debuffs is a legitimate -- clean state (HAS cells), only the 5 unreadable players stay nil -- (UNKNOWN cells). -- Resist filler pass: every readable player who did NOT get an -- explicit resists literal above (the 3 tanks + Simhuntara + Simmage) -- gets small, deterministic, per-school-varied values -- a pure -- function of the player's roster index so re-builds stay -- byte-identical. Small mod bases (7/8/9/10/11) guarantee zeros show -- up repeatedly across the roster (a legitimate 0 resistance, not a -- read failure -- resistsRead is what carries that distinction). The 5 -- unreadable players (Simblade/Simzap/Simveil offline, Simtrue/ -- Simlight read failed) never get resistsRead/resists at all -- -- UNKNOWN cells. for i = 1, table.getn(players) do local p = players[i] if p.auras then p.auras.textures = texturesFor(p.auras) end if p.aurasRead and p.debuffs == nil then p.debuffs = { names = {}, ids = {}, textures = {} } end if p.online ~= false and p.aurasRead and p.resistsRead == nil then p.resistsRead = true p.resists = { [2] = math.mod(i * 3, 7), [3] = math.mod(i * 3 + 2, 8), [4] = math.mod(i * 3 + 4, 9), [5] = math.mod(i * 3 + 6, 10), [6] = math.mod(i * 3 + 8, 11), } end -- Hit filler pass: every readable player WITHOUT an explicit hit -- literal above gets a deterministic set (pure function of the -- roster index, so re-builds stay byte-identical). Small mod -- bases put a few players on the melee cap (8) and leave everyone -- below the spell cap (17) -- the players AT the spell cap are -- the explicit ones. The ranged subtype comes from the player's -- own equipped ranged item, so a wand user stays NOTEXP and a -- thrown-weapon user gets a real ranged column. The 5 unreadable -- players keep no hit table at all -> UNKNOWN cells. if p.online ~= false and p.aurasRead and p.hit == nil then local rangedSub = nil local rangedItem = p.gear and p.gear[18] if type(rangedItem) == "table" then rangedSub = rangedItem.subType end p.hit = { melee = math.mod(i * 2, 9), spell = math.mod(i * 3, 12), schools = {}, hitRead = true, mainSub = HIT_MAIN_SUB[p.class], rangedSub = rangedSub, -- the live starting state for everyone but the own -- character: no inspect reply has arrived yet -- (scan/talents.lua), so the numbers above are gear-only -- and every tooltip on these rows says so talentsRead = false, } -- no race token: the estimate then lands on the plain base -- skill, which is exactly what an unknown/new race token does -- in the live path too end end return { scanAt = now, source = "sim", players = players } end