feat: add backdrop foundation

This commit is contained in:
Nighthawk42
2026-08-24 06:08:11 -04:00
parent c96d5dc9e3
commit 1156ee4dc0
7 changed files with 228 additions and 5 deletions
+1
View File
@@ -15,3 +15,4 @@ Engine\Grid.lua
Engine\Matches.lua
Engine\Cascade.lua
Engine\Scoring.lua
UI\Backdrops.lua
+2
View File
@@ -15,10 +15,12 @@ function addon:Initialize(accountData, profileData)
assert(self.SavedVariables, "SavedVariables module is not loaded")
assert(self.Grid, "Grid module is not loaded")
assert(self.Audio, "Audio module is not loaded")
assert(self.Backdrops, "Backdrops module is not loaded")
self.accountData, self.profileData = self.SavedVariables:Initialize(accountData, profileData)
self.grid = self.Grid:New()
self.audio = self.Audio:New(self.profileData.settings)
self.backdrops = self.Backdrops
self.initialized = true
return self
+154
View File
@@ -0,0 +1,154 @@
local _, addon = ...
local Constants = assert(addon.Constants, "Constants module is not loaded")
local Backdrops = {}
local PRESETS = {
tooltip = {
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
tileSize = 16,
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = 1,
edgeSize = 16,
insets = { top = 5, right = 5, left = 5, bottom = 5 },
},
window = {
bgFile = Constants.IMAGE_ROOT .. "windowBackground",
tileSize = 64,
edgeFile = Constants.IMAGE_ROOT .. "windowBorder",
tile = 1,
edgeSize = 32,
insets = { top = 5, right = 3, left = 5, bottom = 5 },
},
panel = {
bgFile = Constants.IMAGE_ROOT .. "windowBackground",
tileSize = 128,
edgeFile = "Interface\\Glues\\Common\\TextPanel-Border",
tile = 1,
edgeSize = 32,
insets = { top = 3, right = 5, left = 5, bottom = 5 },
},
slider = {
bgFile = Constants.IMAGE_ROOT .. "windowBackground",
tileSize = 64,
edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
tile = 1,
edgeSize = 8,
insets = { top = 5, right = 2, left = 2, bottom = 5 },
},
level = {
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
tileSize = 16,
edgeFile = Constants.IMAGE_ROOT .. "levelBorder",
tile = 1,
edgeSize = 32,
insets = { top = 5, right = 2, left = 2, bottom = 5 },
},
}
local function CopyInsets(insets)
return {
top = insets.top,
right = insets.right,
left = insets.left,
bottom = insets.bottom,
}
end
local function CopyDescriptor(descriptor)
local copy = {}
for key, value in pairs(descriptor) do
if key == "insets" then
copy.insets = CopyInsets(value)
else
copy[key] = value
end
end
return copy
end
local function AddBackdropTemplate(template)
if not template or template == "" then
return "BackdropTemplate"
end
if string.find(template, "BackdropTemplate", 1, true) then
return template
end
return template .. ",BackdropTemplate"
end
local function ApplyColor(frame, methodName, color)
if not color then
return
end
assert(type(color) == "table", methodName .. " color must be a table")
assert(type(frame[methodName]) == "function", "frame does not support " .. methodName)
if color[4] == nil then
frame[methodName](frame, color[1], color[2], color[3])
else
frame[methodName](frame, color[1], color[2], color[3], color[4])
end
end
function Backdrops:CreateDescriptor(presetName, overrides)
presetName = presetName or "tooltip"
local preset = PRESETS[presetName]
assert(preset, "unknown backdrop preset: " .. tostring(presetName))
assert(overrides == nil or type(overrides) == "table", "backdrop overrides must be a table")
local descriptor = CopyDescriptor(preset)
if overrides then
for key, value in pairs(overrides) do
if key == "insets" then
assert(type(value) == "table", "backdrop inset overrides must be a table")
for insetName, insetValue in pairs(value) do
descriptor.insets[insetName] = insetValue
end
else
descriptor[key] = value
end
end
end
return descriptor
end
function Backdrops:Apply(frame, descriptor, backgroundColor, borderColor)
assert(frame ~= nil, "backdrop frame is required")
assert(type(frame.SetBackdrop) == "function", "frame does not support SetBackdrop")
assert(type(descriptor) == "table", "backdrop descriptor must be a table")
frame:SetBackdrop(descriptor)
ApplyColor(frame, "SetBackdropColor", backgroundColor)
ApplyColor(frame, "SetBackdropBorderColor", borderColor)
return frame
end
function Backdrops:CreateFrame(options)
options = options or {}
assert(type(options) == "table", "backdrop frame options must be a table")
local createFrame = options.createFrame or CreateFrame
assert(type(createFrame) == "function", "CreateFrame is unavailable")
local descriptor = options.descriptor or self:CreateDescriptor(options.preset, options.overrides)
local frame = createFrame(
options.frameType or "Frame",
options.name,
options.parent,
AddBackdropTemplate(options.template)
)
self:Apply(frame, descriptor, options.backgroundColor, options.borderColor)
return frame, descriptor
end
Backdrops.PresetNames = {
"tooltip",
"window",
"panel",
"slider",
"level",
}
addon.Backdrops = Backdrops
+3 -1
View File
@@ -6,7 +6,7 @@ This branch contains the analysis-complete, Mainline-first modernization of the
The preserved 8,401-line Mainline source must be analyzed sequentially, in evidence-backed batches, before runtime work begins. All behavior-critical shortened symbols must be resolved and all batches must be complete before any public Lua API, runtime module, modern TOC, packaging, or release work is added.
All 17 batches (lines 18,401) are documented, the cross-batch identifier audit has no remaining `working` or `unresolved` declarations, and the retained Retail API contracts are pinned in `docs/api-baseline.md`. The analysis phase gate is closed. Runtime implementation now includes wire-compatible SavedVariables initialization, deterministic grid/match/cascade/scoring transitions, skills, levels, and legacy-compatible audio cue scheduling.
All 17 batches (lines 18,401) are documented, the cross-batch identifier audit has no remaining `working` or `unresolved` declarations, and the retained Retail API contracts are pinned in `docs/api-baseline.md`. The analysis phase gate is closed. Runtime implementation now includes wire-compatible SavedVariables initialization, deterministic grid/match/cascade/scoring transitions, skills, levels, legacy-compatible audio cue scheduling, and BackdropTemplate-safe UI chrome construction.
## Goal
@@ -19,11 +19,13 @@ The eventual addon will target current Retail/Mainline World of Warcraft while p
- `Bejeweled/sounds/` — immutable legacy sounds.
- `Bejeweled/Core/` — private addon initialization, constants, supported audio playback, and non-destructive SavedVariables defaulting.
- `Bejeweled/Engine/` — deterministic gameplay state; currently the 8×8 grid, swaps, legal moves, legacy cell encoding, stable cascade resolution, legacy score formulas, statistics, skill gains, achievements, and level thresholds.
- `Bejeweled/UI/` — Retail-safe frame/rendering boundaries; currently shared backdrop descriptors and construction.
- `docs/analysis/` — sequential batch reports, identifier ledger, and exact coverage schedule.
- `docs/architecture.md` — module ownership and authoritative runtime data flow.
- `docs/api-baseline.md` — verified Retail API constraints for implementation.
- `tools/test-runtime.lua` and `tools/verify-runtime.ps1` — Lua 5.1-compatible engine tests and TOC verification.
- `plan.md` — the bootstrap specification executed by the root commit.
- `todo.md` — the running implementation roadmap and completed runtime slices.
## Contributing
+2 -2
View File
@@ -1,6 +1,6 @@
# Target architecture (post-analysis)
The analysis phase gate is satisfied. This ownership map now governs runtime implementation; currently implemented modules are `Core/Init.lua`, `Core/Constants.lua`, `Core/Audio.lua`, `Core/SavedVariables.lua`, `Engine/Grid.lua`, `Engine/Matches.lua`, `Engine/Cascade.lua`, and `Engine/Scoring.lua`.
The analysis phase gate is satisfied. This ownership map now governs runtime implementation; currently implemented modules are `Core/Init.lua`, `Core/Constants.lua`, `Core/Audio.lua`, `Core/SavedVariables.lua`, `Engine/Grid.lua`, `Engine/Matches.lua`, `Engine/Cascade.lua`, `Engine/Scoring.lua`, and `UI/Backdrops.lua`.
## Load order and ownership
@@ -12,7 +12,7 @@ The analysis phase gate is satisfied. This ownership map now governs runtime imp
6. `Engine/Matches.lua` — pure legacy-order match detection, axis-overlap reporting, and power/hyper-gem classification; clearing and scoring remain downstream responsibilities.
7. `Engine/Cascade.lua` — transactional clears, matched power-gem expansion, spawned-special preservation, fixed-cell gravity, bounded refill, and repeated transitions to a stable board. It emits logical movement/refill records but owns no animation or scoring.
8. `Engine/Scoring.lua` — legacy score arithmetic, combo/mode/level multipliers, wire-compatible statistics, probabilistic skill gains, one-time achievements, rank advancement, and pending/explicit level transitions. It emits presentation events and owns no frames, text, sound, or chat publishing.
9. `UI/Backdrops.lua` — backdrop-compatible frame construction and shared chrome.
9. `UI/Backdrops.lua` — backdrop-compatible frame construction and fresh-copy presets for tooltip, window, panel, slider, and level-border chrome. Every constructed frame explicitly inherits `BackdropTemplate`.
10. `UI/GemPool.lua` — gem-frame allocation, reuse, and grid-to-frame projection.
11. `UI/Animations.lua` — animation groups, transition timing, and visual effect orchestration.
12. `UI/HUD.lua` — score, timer, level, status, hint, and achievement presentation.
+64 -1
View File
@@ -47,12 +47,72 @@ LoadAddonFile("Bejeweled/Engine/Grid.lua", addon)
LoadAddonFile("Bejeweled/Engine/Matches.lua", addon)
LoadAddonFile("Bejeweled/Engine/Cascade.lua", addon)
LoadAddonFile("Bejeweled/Engine/Scoring.lua", addon)
LoadAddonFile("Bejeweled/UI/Backdrops.lua", addon)
AssertEqual(eventFrame.registeredEvent, "ADDON_LOADED", "initializer event registration")
AssertEqual(addon.Constants.GRID_WIDTH, 8, "grid width")
AssertEqual(addon.Constants.GRID_HEIGHT, 8, "grid height")
AssertEqual(addon.Constants.GEM_COLOR_COUNT, 7, "gem color count")
local windowBackdrop = addon.Backdrops:CreateDescriptor("window")
AssertEqual(windowBackdrop.bgFile, addon.Constants.IMAGE_ROOT .. "windowBackground", "window backdrop texture")
AssertEqual(windowBackdrop.edgeFile, addon.Constants.IMAGE_ROOT .. "windowBorder", "window backdrop border")
AssertEqual(windowBackdrop.insets.right, 3, "window backdrop right inset")
windowBackdrop.insets.right = 99
AssertEqual(addon.Backdrops:CreateDescriptor("window").insets.right, 3, "backdrop preset shared mutable insets")
local customPanelBackdrop = addon.Backdrops:CreateDescriptor("panel", {
edgeSize = 24,
insets = { left = 7 },
})
AssertEqual(customPanelBackdrop.edgeSize, 24, "backdrop scalar override")
AssertEqual(customPanelBackdrop.insets.left, 7, "backdrop inset override")
AssertEqual(customPanelBackdrop.insets.top, 3, "backdrop inset default preservation")
local createdBackdropFrame
local backdropCreateArguments
local function CreateBackdropFrame(frameType, name, parent, template)
backdropCreateArguments = { frameType, name, parent, template }
createdBackdropFrame = {}
function createdBackdropFrame:SetBackdrop(descriptor)
self.descriptor = descriptor
end
function createdBackdropFrame:SetBackdropColor(red, green, blue, alpha)
self.backgroundColor = { red, green, blue, alpha }
end
function createdBackdropFrame:SetBackdropBorderColor(red, green, blue, alpha)
self.borderColor = { red, green, blue, alpha }
end
return createdBackdropFrame
end
local backdropParent = {}
local appliedBackdropFrame, appliedDescriptor = addon.Backdrops:CreateFrame({
name = "BejeweledBackdropTest",
parent = backdropParent,
template = "UIPanelButtonTemplate",
preset = "panel",
backgroundColor = { 0.6, 0.6, 0.6, 1 },
borderColor = { 1, 0.8, 0.45 },
createFrame = CreateBackdropFrame,
})
assert(appliedBackdropFrame == createdBackdropFrame, "backdrop frame identity changed")
assert(appliedDescriptor == createdBackdropFrame.descriptor, "created backdrop descriptor was not applied")
AssertEqual(backdropCreateArguments[1], "Frame", "default backdrop frame type")
AssertEqual(backdropCreateArguments[2], "BejeweledBackdropTest", "backdrop frame name")
assert(backdropCreateArguments[3] == backdropParent, "backdrop frame parent changed")
AssertEqual(backdropCreateArguments[4], "UIPanelButtonTemplate,BackdropTemplate", "backdrop template composition")
AssertEqual(createdBackdropFrame.backgroundColor[4], 1, "backdrop background alpha")
AssertEqual(createdBackdropFrame.borderColor[3], 0.45, "backdrop border blue")
assert(createdBackdropFrame.borderColor[4] == nil, "three-channel border color gained an alpha")
local existingTemplateFrame = addon.Backdrops:CreateFrame({
template = "BackdropTemplate",
createFrame = CreateBackdropFrame,
})
assert(existingTemplateFrame, "existing BackdropTemplate frame was not created")
AssertEqual(backdropCreateArguments[4], "BackdropTemplate", "BackdropTemplate was duplicated")
local playedFiles = {}
local playedSoundKits = {}
local audioSettings = {}
@@ -535,11 +595,14 @@ eventFrame.scripts.OnEvent(eventFrame, "ADDON_LOADED", "Bejeweled", false)
assert(addon.initialized, "addon initialization did not complete")
assert(addon.grid, "addon initialization did not create a grid")
assert(addon.audio, "addon initialization did not create audio")
assert(addon.backdrops == addon.Backdrops, "addon initialization did not install backdrops")
assert(eventFrame.registeredEvent == nil, "initializer event was not unregistered")
local initializedGrid = addon.grid
local initializedAudio = addon.audio
local initializedBackdrops = addon.backdrops
addon:Initialize({}, {})
assert(addon.grid == initializedGrid, "addon initialization is not idempotent")
assert(addon.audio == initializedAudio, "audio initialization is not idempotent")
assert(addon.backdrops == initializedBackdrops, "backdrop initialization is not idempotent")
print("Runtime verification passed: audio, SavedVariables, and deterministic gameplay engine.")
print("Runtime verification passed: UI backdrops, audio, SavedVariables, and deterministic gameplay engine.")
+2 -1
View File
@@ -26,7 +26,8 @@ try {
"Engine\Grid.lua",
"Engine\Matches.lua",
"Engine\Cascade.lua",
"Engine\Scoring.lua"
"Engine\Scoring.lua",
"UI\Backdrops.lua"
)
$actualFiles = @($toc | Where-Object { $_ -match "\.lua$" })
if (Compare-Object -ReferenceObject $expectedFiles -DifferenceObject $actualFiles -SyncWindow 0) {