promotional bannermobile promotional banner

RoyalRevenue

Calculates the most profitable craft for your profession on the auction house and keeps a full gold/item ledger for every character.
Back to Files

v1.40.0

File nameRoyalRevenue-v1.40.0.zip
Uploader
RedsharkRedshark
Uploaded
Oct 19, 2026
Downloads
14
Size
404.1 KB
Flavors
Retail
File ID
9259867
Type
R
Release
Supported game versions
  • 12.1.5
  • 12.1.0
  • 12.0.7
  • 12.0.5

What's new

Royal Revenue

v1.40.0 (2026-10-19)

Full Changelog

  • v1.40.0: forecast vs actual, charge schedule, enchanting exists again, and a crash from infinity
    Five approved ideas plus four bug fixes, one of them the first crash reported by
    someone other than Rafael.
    Acerto tab (forecast vs actual). The addon forecast profit all the time and
    nothing ever checked whether it came true. The comparison engine already existed
    in Craft/Sales.lua, left over from when the Sales tab moved to the Livro, and
    nobody called GetRows(). For every item actually sold in the window it now shows
    forecast against realized, the error with colour, and the impact in gold --
    quantity times difference, net of the cut -- which is the default sort, because
    300% off on one unit is noise and 20% off on 400 units is money. The headline is
    the volume-weighted error, not the mean of loose errors: on the fixture the two
    disagree in direction (-35.15% weighted against +12.85% unweighted).
    TSM in the harness. Three price fixes had shipped with zero diff in the suite,
    not because they were right but because without TSM root.TSMPrice returns nil
    and the whole path never runs. tests/tsmstub.lua reads the price language
    (first, min, max, source * N) from prices the addon itself recorded in
    r.audit.prices, so 755 of 965 recipes get a sale price under test.
    Charge schedule. Charges capped the plan since v1.37.3 but nothing said a charge
    was sitting at the cap, and charges come back ~1/day and do not stack past it.
    Sorted by profit per charge, not by "is full": sorting by full buried the best
    recipe under four full transmutes all losing gold. The warning counts only
    charges worth spending -- 18 of his 20 full charges lose money at today's prices
    -- with a 1g floor, so a 30/30 item at 0.79g per charge does not nag.
    Price by realm came and went. Built as a tab, then measured on real data: it
    flagged 156 of 558 items and 73% of them sell under 1/day against 24% in the
    population, and he had sold exactly zero of the 156. The filter selects the
    cause of divergence, which is illiquidity. The useful kernel moved into
    ns.Provenance, which now reports the sale price as weak when the realm is far
    off the region -- the delve recipe that showed 200k of unrealizable profit.
    Ledger: a crafting order you placed is not income. Every item arriving in order
    mail was booked as revenue, but receiving the output of an order you paid for is
    production, not income, and the auction sale comes later. 19,005.74g of phantom
    revenue on one item. C_Mail.GetCraftingOrderMailInfo tells the two apart, and it
    now books the customer side as a conversion. Auction returns were already
    correct; the test pins that down.
    Own.lua read the invoice count from the 10th return of GetInboxInvoiceInfo and
    it is the 11th -- the 10th is etaMin, which is 0. And count or 1 does not fall
    back when count is 0, because 0 is true in Lua. Every mail sale was stored with
    qty 0 and the total in the unit price field.
    Enchanting did not exist for the addon. An enchant produces an effect, not an
    item; applied to a vellum it produces an item with its own id and name, so
    GetOutputItemID found no output and the scan dropped the recipe. Of 36 stored
    Enchanting recipes, zero were enchants. The game does not expose the vellum id
    (CraftSim keeps a hand-built private table), so the addon learns it: from the
    craft result, which already carried the number and threw it away; from the item
    cache by name; from bags; and from the auction scan. Quality comes only from a
    definitive source -- GetItemCraftedQualityByItemInfo, the same pair of APIs TSM
    uses -- never from id order, which is wrong for 7 of CraftSim's 103 enchants.
    RetierEnchants fixes what was already stored at the wrong tier.
    The reported crash: 17x integer overflow attempting to store nan, with
    money = Infinite reaching the game's GetMoneyString. r.perConc divided
    concProfit by concEff inside a guard that read "if r.concCost and r.concSale",
    and concCost = 0 passes that, because 0 is true in Lua -- the third time in this
    codebase. concCost = 0 is the recipe whose higher quality needs no
    concentration, so gold per point does not exist there; it is a normal craft at a
    better quality, now marked concFree with its profit preserved. Guarded in three
    places: the division, EffectiveConc returning nil instead of zero, and
    Pricing.Finito rejecting nan and infinity in the formatters, so a future
    division by zero shows a dash instead of taking the UI down.
    Also: the measured-yield tooltip divided items by n, where n counts crafts whose
    output was not observed, so it read 7.43 per craft while the calculation used
    7.69. On a 0.5g margin that is half the profit. The multicraft model itself was
    verified against 279 of his crafts and is correct: 34.64% modelled proc chance
    against 34.77% measured.
    Cleanup: the dead History and Bets tab code, the empty Sales.Refresh, and
    recoMinShare documented rather than removed -- it still filters the recipe list,
    it is only bypassed for the concentration plan while the market cap is on.
    Tests: t128 through t135. 104 tests, zero failures, 13 explained diffs.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • v1.39.4: a Portuguese column on an English client, and the blind spot behind it
    Screenshot with the Simulate tab open: tabs and columns in English (Recipes,
    Cost, Skill, Reagents) and one column in Portuguese -- "Qualidade".
    The key had no EN translation, and the strict check.sh I turned on in v1.37.1
    precisely for this did not catch it. The reason is written in CLAUDE.md as a
    known limit: the checker only sees L["literal"]. The column label travels as a
    variable into Head, which resolves L[label] inside, so it escaped.
    tools/loccheck.py now follows indirect labels: for OHead and Head (the addon's
    two functions that resolve L[] internally) it splits the arguments respecting
    quotes and parentheses and checks the label. Verified in practice -- removing
    "Qualidade" from the locale makes the check report
    [Craft\Simulate.lua (via Head)] Qualidade and fail. The limit is no longer a
    limit. It also picked up 34 labels it had not been counting, all already
    translated.
    Simulate's summary line: in the screenshot the cheapest set needs ~396
    concentration and he has 290, so it yields 0 crafts. Saying "the best makes 601g
    more than the cheapest" when the cheapest makes nothing at all is a strange way
    to put it. In that case the line now says the cheapest set does not fit his
    concentration, what it needs and what he has.
    Test: t126.lua gained the insufficient-concentration scenario from the
    screenshot.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • v1.39.3: the quality mark is now a texture, like the game's
    Two screenshots side by side, one from the addon and one from the game: "I want
    the addon's icons to show quality the same way".
    Why v1.39.2 was not enough: there I fixed the atlas proportion, but the mark was
    still a FontString with markup. A FontString carries font metrics -- the line
    has its own height and the glyph sits centred in it with slack around it. That
    is why the mark never reached the corner or the game's size, however high the
    number went. The game draws a texture anchored to the corner, and now so does
    the addon.
    Canvas:Icon gained btn.qtex, an OVERLAY texture anchored TOPLEFT with 1px of
    overhang like the game's. The new option is qTier/qMax (numbers); the old
    quality (text) stays for places where the mark appears inside a sentence.
    ns.QAtlas(q, maxQ) picks the atlas meant for icons -- it prefers
    Professions-Icon-Quality-... (what the game uses over reagents) over ChatIcon,
    which is built for text. ns.QAtlasW returns the width that keeps the ratio.
    The 11 calls that draw a mark over an icon (Queue, Plan, Invest, Simulate,
    Recipes) were converted. ns.QIconOn, added in v1.39.2, became unused and was
    removed -- the dead-function sweep is back to zero.
    The proportion is 50% of the icon height, which is what the game uses on
    reagents (ns.QICON_FRAC). On a 32px icon the mark is 14x16; on a 26px one,
    11x13. Without the atlas in the client it still falls back to text, with no
    error.
    Test: t127.lua now tests the path that actually runs -- the atlas chosen, the
    proportion at 6 icon sizes, and Canvas:Icon showing the texture and clearing the
    text (and hiding the texture when there is no quality).
    Note: outside t127 the suite does not change, since the harness has no game
    atlas. The effect only shows in game; if the mark reads too big or too small it
    is a single number (ns.QICON_FRAC, currently 0.50).
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • v1.39.2: the quality mark was squashed and a different size on every screen
    Asked for: "can you make the quality icon bigger, the same or similar to what
    the game uses -- the one the addon makes has a different proportion from the
    game's default -- and if possible keep it the same for all icons in the addon."
    Cause of the wrong proportion: ns.QIcon built the mark with
    CreateAtlasMarkup(atlas, size, size) -- square. The game's mark is not: the link
    the game itself generates uses 17 high by 15 wide, so forcing a square squashed
    it. The height now leads and the width follows the atlas's real ratio via
    C_Texture.GetAtlasInfo; at height 16 the mark comes out 16x14, like the game's.
    Without that API it still falls back to text ("Q2"), with no error.
    One standard size (ns.QICON_SIZE = 16, was 14) instead of 10, 11, 12 and 14
    picked by hand at each call. The 11 text marks scattered through the addon now
    use the default.
    For a mark drawn over an item icon, ns.QIconOn(q, maxQ, iconSize) uses a fixed
    proportion (ns.QICON_FRAC = 0.45), as the game does. The size used to be
    arbitrary: 12 over a 34px icon is 35%, over a 22px icon is 55% -- the same mark
    looked different on every screen. The 11 places that draw a mark over an icon
    (Queue, Plan, Invest, Simulate, Recipes) were converted.
    Test: t127.lua (the proportion at 4 sizes, the standard size, the fixed
    proportion at 6 icon sizes, a source scan that fails if anyone passes a loose
    size again, and the path with no atlas API).
    Note: the suite does not change outside t127 because the harness has no game
    atlas and the mark falls back to text, which is why t127 stubs the API
    explicitly. The visual effect only shows in game.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • v1.39.1: item icons in the Simulate tab's reagent column
    Asked for: "can you put the item icon with its quality? from what I see there
    is still plenty of screen available, we do not need to save space."
    The reagent column showed only the stars, which forces you to memorise the slot
    order. It now shows the icon of each chosen item, with the quality mark in the
    corner, the quantity and the rarity border, so the combination reads at a
    glance. Each icon carries its own tooltip (item, quantity and that slot's cost),
    and the row's tooltip area starts after them so it does not steal the mouse. The
    text label stays on the row: it is what sorts and what the tests read, since a
    test cannot see a texture.
    Roomier, as asked: rows went from 18 to 32px, the icon is 26px, and the number
    columns were widened (Cost 78->92, Profit 86->102, Profit/point 80->96, Total
    96->112). There was dead space between the reagents and Cost, and a cramped gold
    figure is a figure that gets misread. The reagent column grows with the slot
    count: 5 slots need 168px and fit with 68px to spare before Cost.
    Test: t126.lua now checks that the icons appear (4 combinations x 2 slots plus
    the header one = 9) and that each row knows the item and quality per slot.
    Against the previous code it reports 1 icon instead of 9.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • v1.39.0: a Simulate tab for every reagent quality combination
    Asked for: "which is better, crafting the light potion with the cheapest
    materials or with the best ones, and every combination of material quality, and
    how that affects concentration and how many more times I could craft if I
    change the material."
    Why this cannot be done in your head: a combination moves three things at once
    -- the cost of the reagents, the skill (and with it the quality that comes out
    WITHOUT spending concentration), and the concentration cost of going up one
    quality. Since concentration is the scarce resource, cheap reagents that demand
    more concentration yield fewer crafts and can make less gold overall.
    Craft/Simulate.lua enumerates the cartesian product of each slot's qualities and
    asks the game's own API about each one (Scanner.TryOp, the same call the
    profession panel uses) -- not a formula of our own, which is the only way to
    match what the game will actually do.
    Columns: Reagents, Cost, Skill, Quality, Concentration, Profit, Profit/point,
    Crafts and Total, all sortable. The best by total gets a green background, and a
    line at the top says how much more the best set makes than the cheapest and with
    how many more crafts.
    "Crafts" is the direct answer to the request: floor((concentration - full cost)
    / effective cost) + 1. In the test, moving one slot up a quality costs 8g more
    in reagents and buys 1 more craft and 170.50g more overall.
    A combination that needs no concentration is not limited by it: it shows as
    "free" rather than competing on the same axis. Saying "0 crafts" because the
    bags are empty would be a lie -- the limit there is materials and gold, which
    can be bought.
    It follows the selected recipe just like the audit tab: both read
    ns.auditOut.row, so there is no second selection mechanism to learn. Capped at
    243 combinations (3^5), since each one is an API call and this is an on-demand
    screen.
    Test: t126.lua. t110 and t124 change on purpose: the new tab joins both geometry
    sweeps, clean.
    Note: the .toc gained a file, so WoW needs a restart -- /reload is not enough
    this time.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • v1.38.3: a price that exists on paper and cannot be realised
    Reported: "it counted 200k gold of profit, but it is a gain I cannot realise,
    the recipe does not sell for that on Goldrinn, and on others the value drops to
    1/4" -- it was an enchanting formula that dropped in a delve.
    Where it was: not craft profit. Uriuri's journal holds a 167,865.42g entry on
    14/10, account 3.2.03, item 256753, from "The Labyrinth of Kindo'jan · Delves".
    That is the ledger valuing the drop.
    Cause: Livro/Price.lua used first(DBMarket, DBRegionMarketAvg, DBMinBuyout) --
    your own realm first. In a thin market (a rare formula) a high number on
    Goldrinn becomes "market value", which is exactly what he described when he
    said it is worth a quarter of that elsewhere.
    Fix: the value is now the lower of the realm price and the region average,
    falling back to whichever exists when only one does. It is the same rule the
    craft module already uses in saleSource. The ledger is accounting: what goes in
    has to be what can be realised, not the best number that exists somewhere. The
    file already recognised this trap for gear ("a single absurd listing becomes the
    market value"); now it holds for any item.
    A hole on the craft side, found on the way: saleSource is already min(realm,
    region), but gear is priced by item string with the exact item level, and the
    region rarely has data for that -- so min() does not resolve, the code falls to
    SALE_FALLBACK and takes DBMinBuyout raw. In Uriuri's data DBMinBuyout runs 3.6x
    DBMarket on several leather pieces. Pricing.BoundBySale caps a listing at the
    realm's market value when it exceeds saleMaxOverMarket times it (default 2, 0
    disables).
    Test: t125.lua (the delve case with the real numbers, both sides, and the
    configurable cap).
    Caveats: the 14/10 entry stays as it is -- the change applies going forward, and
    rewriting a past entry is his call. And the suite showed no differences at all,
    because the harness has no TSM and this path is never exercised; t125 tests the
    rule in isolation but the real effect can only be seen in game. That is a known
    coverage gap.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

This mod has no additional files