FIELD GUIDE · FOREVER 1.60 BETA

Build addons for Forever.

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
  1. 01
    Read what the client gives you.

    A documented call may still return a secret or unusable value.

  2. 02
    Keep unknown separate from absent.

    A failed scan does not mean the buff, target, or totem is missing.

  3. 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.