mirror of
https://github.com/Nighthawk42/wow_bejeweled.git
synced 2026-08-30 04:30:22 +00:00
feat: add session level transitions
This commit is contained in:
@@ -91,6 +91,8 @@ function Input:New(grid, gemPool, animations, options)
|
||||
instance.selectedY = nil
|
||||
instance.locked = false
|
||||
instance.paused = false
|
||||
instance.sessionLocked = false
|
||||
instance.sessionLockReason = nil
|
||||
instance.pendingMove = nil
|
||||
instance.lastMove = nil
|
||||
instance.moves = instance.scoringState and (instance.scoringState.moves or 0) or (options.moves or 0)
|
||||
@@ -98,13 +100,32 @@ function Input:New(grid, gemPool, animations, options)
|
||||
end
|
||||
|
||||
function Input:IsLocked()
|
||||
return self.paused or self.locked or self.animations:IsPlaying()
|
||||
return self.paused or self.sessionLocked or self.locked or self.animations:IsPlaying()
|
||||
end
|
||||
|
||||
function Input:IsPaused()
|
||||
return self.paused
|
||||
end
|
||||
|
||||
function Input:SetSessionLocked(locked, reason)
|
||||
assert(type(locked) == "boolean", "input session lock state must be Boolean")
|
||||
if self.sessionLocked == locked then
|
||||
return false
|
||||
end
|
||||
self.sessionLocked = locked
|
||||
self.sessionLockReason = locked and (reason or "session") or nil
|
||||
if locked then
|
||||
self:ClearSelection("session-locked")
|
||||
end
|
||||
self.gemPool:SetInteractive(
|
||||
not self.paused
|
||||
and not self.sessionLocked
|
||||
and not self.locked
|
||||
and not self.animations:IsPlaying()
|
||||
)
|
||||
return true
|
||||
end
|
||||
|
||||
function Input:SetPaused(paused)
|
||||
assert(type(paused) == "boolean", "input pause state must be Boolean")
|
||||
if self.paused == paused then
|
||||
@@ -117,9 +138,11 @@ function Input:SetPaused(paused)
|
||||
self.gemPool:SetInteractive(false)
|
||||
else
|
||||
self.animations:Resume()
|
||||
if not self.locked and not self.animations:IsPlaying() then
|
||||
self.gemPool:SetInteractive(true)
|
||||
end
|
||||
self.gemPool:SetInteractive(
|
||||
not self.sessionLocked
|
||||
and not self.locked
|
||||
and not self.animations:IsPlaying()
|
||||
)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -12,6 +12,8 @@ local CALLBACK_NAMES = {
|
||||
"onPauseChanged",
|
||||
"onSaved",
|
||||
"onRestored",
|
||||
"onLevelTransitionStarted",
|
||||
"onLevelTransitionComplete",
|
||||
}
|
||||
|
||||
local RESTORED_STATE_KEYS = {
|
||||
@@ -35,6 +37,23 @@ local function CopyTable(source)
|
||||
return copy
|
||||
end
|
||||
|
||||
local function CopyRecords(records)
|
||||
local copy = {}
|
||||
for index = 1, #(records or {}) do
|
||||
copy[index] = CopyTable(records[index])
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
local function CopyLevelTransition(record)
|
||||
if not record then
|
||||
return nil
|
||||
end
|
||||
local copy = CopyTable(record)
|
||||
copy.skillEvents = CopyRecords(record.skillEvents)
|
||||
return copy
|
||||
end
|
||||
|
||||
local function ValidateCallbacks(options)
|
||||
local callbacks = {}
|
||||
for index = 1, #CALLBACK_NAMES do
|
||||
@@ -70,9 +89,13 @@ function Session:New(grid, gemPool, animations, options)
|
||||
active = options.active ~= false,
|
||||
paused = false,
|
||||
autoSave = options.autoSave ~= false,
|
||||
deferLevelTransitions = options.deferLevelTransitions == true,
|
||||
levelTransitionSequence = 0,
|
||||
levelTransition = nil,
|
||||
callbacks = ValidateCallbacks(options),
|
||||
}, self)
|
||||
assert(type(instance.timerElapsed) == "number" and instance.timerElapsed >= 0, "session elapsed time must be nonnegative")
|
||||
assert(options.deferLevelTransitions == nil or type(options.deferLevelTransitions) == "boolean", "deferred level-transition state must be Boolean")
|
||||
|
||||
local inputOptions = CopyTable(options.inputOptions)
|
||||
local userMoveComplete = inputOptions.onMoveComplete
|
||||
@@ -116,6 +139,14 @@ function Session:IsLocked()
|
||||
return self.input:IsLocked()
|
||||
end
|
||||
|
||||
function Session:IsLevelTransitionPending()
|
||||
return self.levelTransition ~= nil
|
||||
end
|
||||
|
||||
function Session:GetLevelTransition()
|
||||
return self.levelTransition and CopyLevelTransition(self.levelTransition.record) or nil
|
||||
end
|
||||
|
||||
function Session:SetPaused(paused, reason)
|
||||
assert(type(paused) == "boolean", "session pause state must be Boolean")
|
||||
if self.paused == paused then
|
||||
@@ -164,6 +195,7 @@ end
|
||||
|
||||
function Session:SaveClassicGame(reason)
|
||||
assert(self.active and self.gameMode == Constants.GAME_MODE_CLASSIC, "only an active classic session can be saved")
|
||||
assert(not self.levelTransition, "classic session level transition must complete before saving")
|
||||
assert(not self.input.pendingMove and not self.animations:IsPlaying(), "classic session must be stable before saving")
|
||||
local savedState = self.savedVariables:SaveClassicGame(
|
||||
self.grid,
|
||||
@@ -181,7 +213,80 @@ function Session:SaveClassicGame(reason)
|
||||
return result
|
||||
end
|
||||
|
||||
function Session:BeginLevelTransition(sourceMove)
|
||||
assert(type(sourceMove) == "table", "level transition requires a source move")
|
||||
assert(self.active and sourceMove.status == "complete", "level transition requires a completed active move")
|
||||
assert(not self.levelTransition, "level transition is already active")
|
||||
assert(not self.input.pendingMove and not self.animations:IsPlaying(), "level transition requires a stable board")
|
||||
assert(self.scoringState.levelPending, "scoring state has no pending level")
|
||||
|
||||
self.input:SetSessionLocked(true, "level-transition")
|
||||
self.levelTransitionSequence = self.levelTransitionSequence + 1
|
||||
local record = {
|
||||
status = "started",
|
||||
transitionID = self.levelTransitionSequence,
|
||||
kind = self.gameMode == Constants.GAME_MODE_CLASSIC and "level-up" or "multiplier-up",
|
||||
gameMode = self.gameMode,
|
||||
score = self.scoringState.score,
|
||||
oldLevel = self.scoringState.level,
|
||||
level = self.scoringState.level + 1,
|
||||
oldPointMultiplier = self.scoringState.pointMultiplier,
|
||||
oldPointsToLevelUp = self.scoringState.pointsToLevelUp,
|
||||
sound = "LevelUp",
|
||||
skillEvents = {},
|
||||
}
|
||||
self.levelTransition = {
|
||||
record = record,
|
||||
sourceMove = sourceMove,
|
||||
}
|
||||
sourceMove.levelTransition = CopyLevelTransition(record)
|
||||
self.input:PlaySound(record.sound)
|
||||
self:Notify("onLevelTransitionStarted", CopyLevelTransition(record))
|
||||
|
||||
if not self.deferLevelTransitions and self.levelTransition then
|
||||
self:CompleteLevelTransition()
|
||||
end
|
||||
return CopyLevelTransition(record)
|
||||
end
|
||||
|
||||
function Session:CompleteLevelTransition()
|
||||
local activeTransition = self.levelTransition
|
||||
assert(activeTransition, "no level transition is active")
|
||||
local advanced = Scoring:AdvanceLevel(self.scoringState, self.profile, {
|
||||
random = self.input.random,
|
||||
skillLimit = self.input.skillLimit,
|
||||
})
|
||||
self.levelTransition = nil
|
||||
self.input:SetSessionLocked(false)
|
||||
|
||||
local started = activeTransition.record
|
||||
local result = {
|
||||
status = "complete",
|
||||
transitionID = started.transitionID,
|
||||
kind = started.kind,
|
||||
gameMode = started.gameMode,
|
||||
score = started.score,
|
||||
oldLevel = advanced.oldLevel,
|
||||
level = advanced.level,
|
||||
oldPointMultiplier = started.oldPointMultiplier,
|
||||
pointMultiplier = advanced.pointMultiplier,
|
||||
oldPointsToLevelUp = started.oldPointsToLevelUp,
|
||||
pointsToLevelUp = advanced.pointsToLevelUp,
|
||||
skillEvents = CopyRecords(advanced.skillEvents),
|
||||
}
|
||||
activeTransition.sourceMove.levelTransitionComplete = CopyLevelTransition(result)
|
||||
if self.autoSave and self.active and self.gameMode == Constants.GAME_MODE_CLASSIC then
|
||||
activeTransition.sourceMove.saveResult = self:SaveClassicGame("level-transition")
|
||||
end
|
||||
self:Notify("onLevelTransitionComplete", CopyLevelTransition(result))
|
||||
return CopyLevelTransition(result)
|
||||
end
|
||||
|
||||
function Session:HandleMoveComplete(result)
|
||||
if self.active and result.status == "complete" and self.scoringState.levelPending then
|
||||
self:BeginLevelTransition(result)
|
||||
return
|
||||
end
|
||||
if self.autoSave
|
||||
and self.active
|
||||
and self.gameMode == Constants.GAME_MODE_CLASSIC
|
||||
@@ -195,6 +300,7 @@ function Session:RestoreClassicGame(options)
|
||||
assert(type(options) == "table", "classic restore options must be a table")
|
||||
local pauseAfterRestore = options.paused
|
||||
assert(pauseAfterRestore == nil or type(pauseAfterRestore) == "boolean", "restore pause state must be Boolean")
|
||||
assert(not self.levelTransition, "cannot restore during a level transition")
|
||||
assert(not self.input.pendingMove and not self.animations:IsPlaying(), "cannot restore during an active move")
|
||||
local wasPaused = self.paused
|
||||
local restored = self.savedVariables:RestoreClassicGame(self.grid, self.profile, self:ResolvePlayerName())
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Bejeweled modernization
|
||||
|
||||
This branch contains the analysis-complete, Mainline-first modernization of the legacy World of Warcraft Bejeweled addon. Its Retail TOC and headless runtime foundation are installable for development, but the addon is not yet playable because level/game-over session states, HUD, and menu integration still need to be restored.
|
||||
This branch contains the analysis-complete, Mainline-first modernization of the legacy World of Warcraft Bejeweled addon. Its Retail TOC and headless runtime foundation are installable for development, but the addon is not yet playable because game-over session state, HUD, and menu integration still need to be restored.
|
||||
|
||||
## Status and phase gate
|
||||
|
||||
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 1–8,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 and authenticated Classic restoration, deterministic grid/match/cascade/scoring transitions, pause/resume and stable-move autosave coordination, click selection, validated swap/rollback sessions, skills, levels, legacy-compatible audio cue scheduling, BackdropTemplate-safe UI chrome, persistent grid-to-gem texture projection, cancellable cascade sequencing, power/hyper presentation, and pooled explosion/lightning playback.
|
||||
All 17 batches (lines 1–8,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 and authenticated Classic restoration, deterministic grid/match/cascade/scoring transitions, pause/resume and stable-state autosave coordination, session-owned level-transition handoffs, click selection, validated swap/rollback sessions, skills, levels, legacy-compatible audio cue scheduling, BackdropTemplate-safe UI chrome, persistent grid-to-gem texture projection, cancellable cascade sequencing, power/hyper presentation, and pooled explosion/lightning playback.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -18,7 +18,7 @@ The eventual addon will target current Retail/Mainline World of Warcraft while p
|
||||
- `Bejeweled/images/` — immutable legacy images and bundled font.
|
||||
- `Bejeweled/sounds/` — immutable legacy sounds.
|
||||
- `Bejeweled/Core/` — private addon initialization, constants, supported audio playback, non-destructive SavedVariables defaulting, and legacy-authenticated Classic save encoding.
|
||||
- `Bejeweled/Engine/` — deterministic gameplay state; currently the 8×8 grid, selection/swap and pause/resume sessions, authenticated restoration, legal moves, legacy cell encoding, stable cascade resolution, legacy score formulas, move accounting, statistics, skill gains, achievements, and level thresholds.
|
||||
- `Bejeweled/Engine/` — deterministic gameplay state; currently the 8×8 grid, selection/swap and pause/resume sessions, authenticated restoration, session-locked level advancement, legal moves, legacy cell encoding, stable cascade resolution, legacy score formulas, move accounting, statistics, skill gains, achievements, and level thresholds.
|
||||
- `Bejeweled/UI/` — Retail-safe frame/rendering boundaries; currently shared backdrop construction, board tiles, persistent gem-frame projection, and recorded cascade-transition playback.
|
||||
- `docs/analysis/` — sequential batch reports, identifier ledger, and exact coverage schedule.
|
||||
- `docs/architecture.md` — module ownership and authoritative runtime data flow.
|
||||
|
||||
@@ -12,8 +12,8 @@ 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, target-color and double-hyper activation, spawned-special preservation, fixed-cell gravity, bounded refill, and repeated transitions to a stable board. It emits immutable award, lightning-link, movement, and 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. `Engine/Input.lua` — authoritative selection and move coordination, optimistic adjacent swaps, match validation, immediate engine rollback for invalid moves, legacy hyper activation, move accounting, cascade/scoring handoff, pause gating, and presentation locking.
|
||||
10. `Engine/Session.lua` — Classic session lifetime, pause/resume state, elapsed-time gating, stable-move autosave, authenticated restore, and coordination between input, scoring, persistence, grid projection, and animation clocks. Level and game-over transitions remain follow-on session work.
|
||||
9. `Engine/Input.lua` — authoritative selection and move coordination, optimistic adjacent swaps, match validation, immediate engine rollback for invalid moves, legacy hyper activation, move accounting, cascade/scoring handoff, pause gating, and session/presentation locking.
|
||||
10. `Engine/Session.lua` — Classic session lifetime, pause/resume state, elapsed-time gating, stable-state autosave, authenticated restore, and coordination between input, scoring, persistence, grid projection, and animation clocks. It exclusively consumes pending scoring levels after a stable move, emits copied start/complete presentation records, retains the input lock while presentation is deferred, advances level arithmetic on completion, and only then autosaves. Game-over transitions remain follow-on session work.
|
||||
11. `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`.
|
||||
12. `UI/GemPool.lua` — fixed allocation and reuse of the 64 interactive gem frames, the sixteen board-art tiles, input-handler attachment, selection projection, and change-aware projection from authoritative grid cells into normal/hyper texture layers. Power-gem overlays remain `UI/Animations.lua` ownership.
|
||||
13. `UI/Animations.lua` — deterministic swap/rollback and clear/gravity/refill plans, reusable animation groups, session-controlled pause/resume, interaction locking, cancellation, and final-grid normalization. It also owns the legacy-cadence 40-frame hyper atlas, counter-rotating/cross-faded power layers, pooled 16-frame explosion atlas, and pooled 15-tick lightning lines that gate settling. Shards, lightwaves, hint bounce, and floating text remain follow-on presentation work; current fall timings are explicit modernization defaults pending in-game tuning.
|
||||
@@ -22,7 +22,7 @@ The analysis phase gate is satisfied. This ownership map now governs runtime imp
|
||||
|
||||
## Data flow
|
||||
|
||||
`SavedVariables initialization/restore → session-owned lifetime → engine-owned deterministic grid state → input/match/cascade/scoring transitions → UI rendering and animation → stable-state autosave and HUD/audio feedback`
|
||||
`SavedVariables initialization/restore → session-owned lifetime → engine-owned deterministic grid state → input/match/cascade/scoring transitions → UI rendering and animation → session-owned level/game-over gates → stable-state autosave and HUD/audio feedback`
|
||||
|
||||
The engine will own authoritative game state. UI frames will render state and report input; they will not become the source of gameplay truth. Audio and HUD reactions consume completed transitions.
|
||||
|
||||
|
||||
+75
-1
@@ -994,14 +994,23 @@ local sessionState = addon.Scoring:NewState(addon.Constants.GAME_MODE_CLASSIC, {
|
||||
local sessionPauseEvents = {}
|
||||
local sessionSaveEvents = {}
|
||||
local sessionRestoreEvents = {}
|
||||
local sessionLevelStartedEvents = {}
|
||||
local sessionLevelCompleteEvents = {}
|
||||
local sessionSounds = {}
|
||||
local session = addon.Session:New(sessionGrid, sessionPool, sessionAnimations, {
|
||||
profile = sessionProfile,
|
||||
playerName = "Nighthawk",
|
||||
scoringState = sessionState,
|
||||
timerElapsed = 42.75,
|
||||
deferLevelTransitions = true,
|
||||
inputOptions = {
|
||||
random = MakeRandom(4810),
|
||||
requireLegalMove = false,
|
||||
audio = {
|
||||
Play = function(_, soundName)
|
||||
sessionSounds[#sessionSounds + 1] = soundName
|
||||
end,
|
||||
},
|
||||
},
|
||||
onPauseChanged = function(result)
|
||||
sessionPauseEvents[#sessionPauseEvents + 1] = result
|
||||
@@ -1012,6 +1021,12 @@ local session = addon.Session:New(sessionGrid, sessionPool, sessionAnimations, {
|
||||
onRestored = function(result)
|
||||
sessionRestoreEvents[#sessionRestoreEvents + 1] = result
|
||||
end,
|
||||
onLevelTransitionStarted = function(result)
|
||||
sessionLevelStartedEvents[#sessionLevelStartedEvents + 1] = result
|
||||
end,
|
||||
onLevelTransitionComplete = function(result)
|
||||
sessionLevelCompleteEvents[#sessionLevelCompleteEvents + 1] = result
|
||||
end,
|
||||
})
|
||||
local manualSessionSave = session:SaveClassicGame("test-save")
|
||||
local savedSessionState = manualSessionSave.savedState
|
||||
@@ -1103,6 +1118,65 @@ AssertEqual(sessionMove.status, "complete", "resumed session move completion sta
|
||||
assert(sessionMove.saveResult and sessionMove.saveResult.status == "saved", "stable session move was not auto-saved")
|
||||
AssertEqual(#sessionSaveEvents, 2, "session save callback count")
|
||||
AssertEqual(sessionProfile.settings.savedState[9][4], sessionState.moves, "auto-saved move count")
|
||||
|
||||
sessionState.score = 500
|
||||
sessionState.pointsToLevelUp = 500
|
||||
sessionState.level = 1
|
||||
sessionState.pointMultiplier = 1
|
||||
sessionState.levelPending = true
|
||||
local levelSourceMove = { status = "complete" }
|
||||
session:HandleMoveComplete(levelSourceMove)
|
||||
assert(session:IsLevelTransitionPending(), "session did not retain the presentation handoff")
|
||||
assert(session:IsLocked(), "pending level transition did not lock input")
|
||||
assert(not sessionPool:GetFrame(1, 1).mouseEnabled, "pending level transition left gems interactive")
|
||||
AssertEqual(sessionState.level, 1, "level advanced before presentation completion")
|
||||
assert(sessionState.levelPending, "pending level flag was consumed before presentation completion")
|
||||
AssertEqual(#sessionLevelStartedEvents, 1, "level-transition start callback count")
|
||||
AssertEqual(sessionLevelStartedEvents[1].kind, "level-up", "classic level-transition kind")
|
||||
AssertEqual(sessionLevelStartedEvents[1].oldLevel, 1, "level-transition starting level")
|
||||
AssertEqual(sessionLevelStartedEvents[1].level, 2, "level-transition target level")
|
||||
AssertEqual(sessionSounds[#sessionSounds], "LevelUp", "level-transition sound")
|
||||
sessionLevelStartedEvents[1].level = 99
|
||||
levelSourceMove.levelTransition.oldLevel = 99
|
||||
AssertEqual(session:GetLevelTransition().level, 2, "callback mutated active level-transition record")
|
||||
AssertEqual(session:GetLevelTransition().oldLevel, 1, "source move mutated active level-transition record")
|
||||
local transitionSaveSucceeded = pcall(function()
|
||||
session:SaveClassicGame("during-level-transition")
|
||||
end)
|
||||
assert(not transitionSaveSucceeded, "session saved an incomplete level transition")
|
||||
session:Pause("level-transition")
|
||||
session:Resume("level-transition")
|
||||
assert(session:IsLocked(), "pause cycle released the level-transition lock")
|
||||
assert(not sessionPool:GetFrame(1, 1).mouseEnabled, "pause cycle re-enabled input during level transition")
|
||||
|
||||
local completedLevelTransition = session:CompleteLevelTransition()
|
||||
AssertEqual(completedLevelTransition.status, "complete", "level-transition completion status")
|
||||
AssertEqual(completedLevelTransition.level, 2, "session-advanced level")
|
||||
AssertEqual(completedLevelTransition.pointMultiplier, 1.5, "session-advanced point multiplier")
|
||||
AssertEqual(completedLevelTransition.pointsToLevelUp, 1975, "session-advanced level threshold")
|
||||
assert(not sessionState.levelPending, "completed transition retained the pending level flag")
|
||||
assert(not session:IsLevelTransitionPending(), "completed level transition remained active")
|
||||
assert(not session:IsLocked(), "completed level transition left input locked")
|
||||
assert(sessionPool:GetFrame(1, 1).mouseEnabled, "completed level transition left gems disabled")
|
||||
AssertEqual(#sessionLevelCompleteEvents, 1, "level-transition completion callback count")
|
||||
AssertEqual(#sessionSaveEvents, 3, "level-transition autosave callback count")
|
||||
assert(levelSourceMove.saveResult and levelSourceMove.saveResult.status == "saved", "level transition was not auto-saved")
|
||||
AssertEqual(sessionProfile.settings.savedState[9][2], 1975, "auto-saved level threshold")
|
||||
AssertEqual(sessionProfile.settings.savedState[9][3], 2, "auto-saved advanced level")
|
||||
|
||||
session.deferLevelTransitions = false
|
||||
sessionState.score = 1975
|
||||
sessionState.levelPending = true
|
||||
local automaticLevelMove = { status = "complete" }
|
||||
session:HandleMoveComplete(automaticLevelMove)
|
||||
assert(not session:IsLevelTransitionPending(), "automatic level transition remained deferred")
|
||||
AssertEqual(sessionState.level, 3, "automatic session-advanced level")
|
||||
AssertEqual(sessionState.pointMultiplier, 2, "automatic session-advanced point multiplier")
|
||||
AssertEqual(sessionState.pointsToLevelUp, 5550, "automatic session-advanced threshold")
|
||||
AssertEqual(#sessionLevelStartedEvents, 2, "automatic level-transition start callback count")
|
||||
AssertEqual(#sessionLevelCompleteEvents, 2, "automatic level-transition completion callback count")
|
||||
AssertEqual(#sessionSaveEvents, 4, "automatic level-transition autosave callback count")
|
||||
assert(automaticLevelMove.saveResult, "automatic level transition omitted stable-state autosave")
|
||||
end
|
||||
TestSessionRestore()
|
||||
|
||||
@@ -1480,4 +1554,4 @@ assert(addon.animationFactory == initializedAnimationFactory, "Animations initia
|
||||
assert(addon.inputFactory == initializedInputFactory, "Input initialization is not idempotent")
|
||||
assert(addon.sessionFactory == initializedSessionFactory, "Session initialization is not idempotent")
|
||||
|
||||
print("Runtime verification passed: pause/restore sessions, input, cascade animation, gem projection, UI backdrops, audio, SavedVariables, and deterministic gameplay engine.")
|
||||
print("Runtime verification passed: pause/restore/level-transition sessions, input, cascade animation, gem projection, UI backdrops, audio, SavedVariables, and deterministic gameplay engine.")
|
||||
|
||||
Reference in New Issue
Block a user