A practical path from your first .toc file to a dependable in-game helper. Learn what the Forever client actually exposes, what becomes restricted in combat, and how to test without guessing.
Based on the extracted Forever API, this addon's tests, and observed beta behavior. Checked 23 September 2026.
THE THREE RULESFC / 01
01
Read what the client gives you.
A documented call may still return a secret or unusable value.
02
Keep unknown separate from absent.
A failed scan does not mean the buff, target, or totem is missing.
03
Test the live situation.
Mocked checks cannot prove combat behavior or saved settings.
01 / START HERE
Your first Forever addon
A tiny slash-command addon teaches the two essentials: the package manifest and initialization after ADDON_LOADED. Keep the folder and .toc basename identical.
1
Create Interface/AddOns/HelloForever/ inside the client you actually play. For this repo's beta install, that is _classic_beta_/Interface/AddOns/.
2
Place these two files in that folder. The interface number below matches the tested 1.60 beta build; check your current client before publishing for another build.
HelloForever.toc
## Interface: 16001
## Title: Hello Forever
## Notes: A first addon for WoW Forever
## SavedVariables: HelloForeverDB
HelloForever.lua
HelloForever.lua
local addonName = ...
local frame = CreateFrame("Frame")
frame:RegisterEvent("ADDON_LOADED")
frame:SetScript("OnEvent", function(self, event, loadedName)
if loadedName ~= addonName then return end
if type(HelloForeverDB) ~= "table" then
HelloForeverDB = {}
end
if HelloForeverDB.enabled == nil then
HelloForeverDB.enabled = true
end
self:UnregisterEvent("ADDON_LOADED")
print("Hello Forever loaded. Type /hf")
end)
SLASH_HELLOFOREVER1 = "/hf"
SlashCmdList.HELLOFOREVER = function()
HelloForeverDB.enabled = not HelloForeverDB.enabled
print("Hello Forever:", HelloForeverDB.enabled and "on" or "off")
end
3
Load the addon in game, run /hf, then /reload. For persistence, change it again, log out normally, return, and inspect the SavedVariables file. A working command alone does not prove a saved preference survives a session.
Build the smallest useful feature first.
Add one readable cue, such as a known player buff or power percentage. Make it optional, stop its events and polling when disabled, then test it with and without a target and in combat.
02 / THE IMPORTANT DIFFERENCE
Read safely. Display honestly.
Forever's modern client can hand an addon a secret value. Lua must not branch, calculate, format, or inspect that value. pcall catches errors, but a successful call can still return a secret.
≠
Unknown is not missing
Use three states: a readable match, a complete readable scan with no match, and unavailable. Never turn a restricted aura scan into a “missing buff” alert.
◫
Native UI can sometimes render it
Where the API explicitly permits secret numeric arguments, a native font string or status bar can display a value directly. The addon must not do arithmetic with it.
Guard a single numeric return
local function ReadNumber(fn, ...)
if type(fn) ~= "function" then return nil end
local ok, value = pcall(fn, ...)
if not ok or (issecretvalue and issecretvalue(value)) then
return nil
end
if type(value) ~= "number" or value ~= value
or value == math.huge or value == -math.huge then
return nil
end
return value
end
This helper is for a single return. Multi-return APIs need every return position checked, including positions after a nil hole. Tables need their fields checked separately. See the repo's Core.Call for a multi-return wrapper.
03 / WORKING WITH THE CLIENT
API field guide
Use these patterns as starting points, then verify the exact API and restrictions against the build you target. The Forever UI source is the reference; mainline examples alone are insufficient.
01
Learned spells
Use C_SpellBook and C_Spell to discover what this character actually learned. Consider base spell IDs, overrides, passive entries, localized names, and ranks.
Refresh on SPELLS_CHANGED or LEARNED_SPELL_IN_SKILL_LINE. The user's beta client rejected LEARNED_SPELL_IN_TAB.
02
Auras & ownership
C_UnitAuras.GetAuraDataByIndex can be restricted. A “missing” result requires a complete readable scan. For a target debuff, check that its source is the player.
If any scanned aura is unreadable, report unavailable rather than absent.
03
Power & shields
Use readable UnitPower/UnitPowerMax, or UnitPowerPercent for a client percentage. UnitGetTotalAbsorbs reports combined absorbs, not one shield's original capacity.
A shield meter scaled to max health means “% HP covered,” not “% shield left.”
04
Range & facing
UnitDistanceSquared has a second checkedDistance return. Only a readable true validates exact yards. Spell-range checks give brackets.
A failed positive-minimum shot check may mean too close or too far. Coordinates are not a workaround for restricted combat range.
05
Totems
GetTotemInfo and GetTotemTimeLeft can become secret. In one beta observation, haveTotem=true accompanied an empty name and zero time while a totem was visible.
Treat that as unknown. A ~ timer is an estimate only after that spell's duration was previously observed.
06
Weapon enchants
C_Item.GetWeaponEnchantInfo returns enchant records for a weapon slot, not the old Classic tuple. Read hasEnchant and timeLeft defensively.
The enchant timeLeft is milliseconds; divide by 1,000 only when readable.
Exact distance only with a valid check
local ok, squared, checked = pcall(UnitDistanceSquared, "target")
if ok and not (issecretvalue and
(issecretvalue(squared) or issecretvalue(checked)))
and checked == true and type(squared) == "number"
and squared == squared and squared >= 0
and squared < math.huge then
local yards = math.sqrt(squared)
-- Display yards only in this branch.
end
04 / LEARNED THE HARD WAY
Pitfalls to catch early
Most beta breakage looks like a plausible value that means the wrong thing. Make the uncertainty visible during development.
01
Registering a remembered event
The beta rejected LEARNED_SPELL_IN_TAB. Check the Forever event documentation before registration. A Shaman combat-log experiment also produced a blocked-action warning in the user's client.
02
Guessing from a familiar spell ID
Forever talents, ranks, and racials differ from older Classic versions. Discover the live spellbook and use readable metadata. Treat a selective cue catalog as a helper, not a complete spell database.
03
Calling “not readable” zero
Unavailable mana, aura duration, absorb amount, and range should stay unavailable. Do not calculate with a secret value or show a fake percentage.
04
Mixing up timers
A summoned totem's remaining lifetime differs from the spell's action-button cooldown. Enchant time-left uses milliseconds. Label observations and estimates separately.
05
Assuming settings persisted
A UI checkbox may work until reload but fail after logout. Check the saved global's TOC declaration, migration logic, and the actual SavedVariables file after a normal session boundary.
06
Trusting a passing mock too much
Unit tests can prove your decision logic. They cannot prove a restricted API is readable in combat, an event is accepted, or a bar is legible on the live client.
05 / SHIP SOMETHING RELIABLE
A practical build loop
Give each feature a clear evidence path, a small UI surface, and a way to turn it off.
01 / CHECK
Inspect the client
Read the matching Forever API signature and its secret annotations. Confirm the actual event and return positions before coding.
02 / MODEL
Keep states honest
Represent present, absent, and unknown separately. Only show alerts from trustworthy observations.
03 / LIMIT
Control the lifecycle
Register events and poll only for the active, enabled feature. Suspend during death and world transitions; clear stale target data.
04 / TEST
Exercise both layers
Test logic with readable, missing, malformed, and secret values. Then test in game in and out of combat, across reload and logout.
05 / PACKAGE
Keep upgrades safe
Preserve stable folder, TOC, and SavedVariables names. Compare the packaged files with the installed files and keep existing settings.
06 / RELEASE
Publish what works
Version runtime changes, document limitations, and verify the downloaded release. Treat the user's live confirmation as separate from CI.
Explore a working example.
Forever Classmate source implements class selection, guarded reads, independently switchable blocks, and a release pipeline. Its tests cover secret, missing, and malformed data. The addon itself supports all nine classes.
06 / KEEP VERIFYING
References & scope
This guide documents a Forever 1.60 beta snapshot. Check current client behavior before applying an example to a later build.