--- @section Ascension local ASCENSION_MAX_LEVEL = 9 local ASCENSION_WORD = "ASCENSION" local _increased_this_cycle = false local _flash_active = false local _flash_timer = 0 local _flash_total = 0 local FLASH_DURATION = 120 local _fade_active = false local _fade_timer = 0 local FADE_DURATION = 120 local FADE_COLORS = nil --- Gets initial ascension state. --- @within Ascension --- @return result table Initial ascension state.
--- Fields:
--- * level (number) Current ascension level (0-9). function Ascension.get_initial() _increased_this_cycle = false return { level = 0, } end --- Gets the current ascension level. --- @within Ascension --- @return number The current ascension level (0-9). function Ascension.get_level() if not Context or not Context.ascension then return 0 end return Context.ascension.level end --- Gets the maximum ascension level. --- @within Ascension --- @return number The maximum ascension level. function Ascension.get_max_level() return ASCENSION_MAX_LEVEL end --- Increases the ascension level by 1, clamped to the max. --- @within Ascension function Ascension.increase() if not Context or not Context.ascension then return end Context.ascension.level = math.min(ASCENSION_MAX_LEVEL, Context.ascension.level + 1) _increased_this_cycle = true end --- Returns true if ascension was incremented since the last consume call. --- @within Ascension --- @return boolean Whether ascension increased this cycle. function Ascension.did_increase() return _increased_this_cycle end --- Consumes the increase flag, returning its value and resetting it. --- @within Ascension --- @return boolean Whether ascension had increased this cycle. function Ascension.consume_increase() local result = _increased_this_cycle _increased_this_cycle = false return result end --- Returns true when the ascension meter is fully complete (level 10). --- @within Ascension --- @return boolean Whether the cycle can be broken. function Ascension.is_complete() return Ascension.get_level() >= ASCENSION_MAX_LEVEL end --- Draws the ascension meter as individual letters of "ASCENSION". --- Each letter lights up per level. Drawn beneath existing meter bars. --- @within Ascension --- @param x number Left x position. --- @param y number Top y position. --- @param options table Optional overrides: lit_color, dim_color, spacing. function Ascension.draw(x, y, options) if not Context or not Context.ascension then return end options = options or {} local level = Context.ascension.level if level < 1 then return end local lit_color = options.lit_color or Config.colors.white local spacing = options.spacing or 5 for i = 1, level do local ch = ASCENSION_WORD:sub(i, i) local color if i == level and _fade_active then color = Ascension.get_fade_color() else color = lit_color end print(ch, x + (i - 1) * spacing, y, color, false, 1, true) end end --- Returns the current fade-in color based on progress through the palette. --- @within Ascension --- @return number The palette color index for the current fade step. function Ascension.get_fade_color() if not FADE_COLORS then FADE_COLORS = { Config.colors.black, Config.colors.dark_grey, Config.colors.light_grey, Config.colors.white, } end if not _fade_active then return Config.colors.white end local progress = math.min(_fade_timer / FADE_DURATION, 1) local idx = math.floor(progress * (#FADE_COLORS - 1)) + 1 return FADE_COLORS[idx] end --- Starts the fade-in effect for the most recently gained letter. --- @within Ascension function Ascension.start_fade() _fade_active = true _fade_timer = 0 end --- Starts the ascension flash effect. --- @within Ascension function Ascension.start_flash() _flash_active = true _flash_timer = 0 _flash_total = FLASH_DURATION end --- Updates and draws the ascension flash overlay. --- Call once per frame from the main loop. --- @within Ascension function Ascension.draw_flash() if not _flash_active then return end _flash_timer = _flash_timer + 1 local sw = Config.screen.width local sh = Config.screen.height local progress = _flash_timer / FLASH_DURATION local pulse = math.abs(math.sin(progress * math.pi * 6)) local flash_color = (pulse > 0.5) and Config.colors.white or Config.colors.light_grey rect(0, 0, sw, sh, flash_color) if _flash_timer >= _flash_total then _flash_active = false Ascension.start_fade() end end --- Updates the fade-in timer. Call once per frame from the main loop. --- @within Ascension function Ascension.update_fade() if not _fade_active then return end _fade_timer = _fade_timer + 1 if _fade_timer >= FADE_DURATION then _fade_active = false end end --- Returns whether a flash effect is currently active. --- @within Ascension --- @return boolean Whether the flash is playing. function Ascension.is_flashing() return _flash_active end