Skip to content

Dev Log: 2026-07-12

"The confirmation dialog was always going to say yes. The point was never the answer, it was making sure the player saw the question."


Limit breaks now ask before they fire

Katyusha's Iron Curtain has had a confirmation prompt for a while: activate now, yes or no. Storm Run and Broadside never got the same treatment, mostly because the original prompt was hardcoded to Iron Curtain's wording. This build generalizes it to all three.

Tap a limit break and, instead of it firing immediately, you get a dialog with the ability's real numbers for that cast: range, damage percentage, block count, whatever applies. Not generic flavor text, the actual values computed for the unit's current stats:

const _LIMIT_BREAK_CONFIRM_W := 720
const _LIMIT_BREAK_CONFIRM_H := 460

func _prompt_limit_break_confirm(unit: Unit) -> bool:
    var unit_id := unit.unit_id
    var info: Dictionary = UnitDetail.UNIT_INFO_KEYS.get(unit_id, {})
    _confirm_dialog.show_confirm(
        tr(info.get("limit_name", "HUD_LB_DEFAULT")),
        SkillSystem.limit_break_confirm_body(unit_id),
        tr("DLG_LB_ACTIVATE"),
        tr("HUD_CANCEL"),
        "", "", _LIMIT_BREAK_CONFIRM_W, _LIMIT_BREAK_CONFIRM_H,
        tr("DLG_LB_DONT_ASK"),
    )
    var accepted: bool = await _confirm_dialog.confirmed
    if accepted and _confirm_dialog.dont_ask_again_checked():
        ConfigManager.set_limit_break_confirm_dismissed(unit_id, true)
    return accepted

SkillSystem.limit_break_confirm_body() builds the body text per unit, so Iron Curtain's dialog talks about block counts and reflected damage, Storm Run's talks about dash range and cells hit, Broadside's talks about the 3x3 blast radius, all pulled from the same balance numbers the ability itself will use a moment later. A limit break is the biggest swing a turn can make, and until now the only ability that paused to show you the math before committing was Katyusha's. The other two just went off the moment you tapped the target.

The dialog has a "don't ask again for this limit break" checkbox, tracked per unit, and a new "Reset Limit Break Confirmations" button under Options brings all three back if you opted out and change your mind later.


Dragging the limit break start tile

This one is about a case that is easy to miss in testing and mildly annoying to hit as a player: Storm Run and Broadside both need Nadeshiko or Maria to be standing somewhere specific before the ability fires, a straight line for the dash, a center point for the barrage. When the unit's current position is already out of range of the tile you picked, the game auto-walks to the nearest valid spot and shows you a preview route ending there.

The problem is "nearest valid spot" is a guess, and it is not always the guess the player would have made. Multiple tiles can reach the same target within the ability's range, and the auto-picked one might put the unit somewhere you would rather not stand for the next enemy reaction.

You can now drag that end-of-route marker to a different tile, as long as it is still within range of the locked target and actually walkable. Drop it somewhere invalid and the drag is silently rejected, the marker snaps back to wherever it last was:

func _try_lb_start_drag_move(cell: Vector2i) -> void:
    if selected == null or not is_instance_valid(selected) or grid == null:
        return
    if not grid.is_in_bounds(cell) or cell == _get_lb_anchor_cell(selected):
        return
    if cell not in move_cells or not _can_lb_target_from(selected, _pending_limit_target, cell):
        return  # reject: out of range, unreachable, or blocked - keep current start tile
    var path: Array[Vector2i] = grid.get_grid_path_ex(
        selected.grid_pos, cell, _obstacle_cells(selected), selected.water_only,
        selected.can_fly, _drone_danger_cells(selected))
    if path.size() < 2:
        return
    _pending_lb_reposition_path = path.duplicate()
    _lb_planning_anchor = cell
    _refresh_limit_target_from_anchor(selected)
    _redraw_limit_preview_overlays(selected)

The target tile itself never moves during this, only where the unit stands to reach it. It reuses the camera input layer's existing press-and-drag-to-draw-a-path machinery rather than inventing a new gesture, so it feels like every other drag interaction in the game, it just resolves differently once it lands.


A No-Queue Challenge mode

Challenge Mode (fixed checkpoints, fixed unit stats, score from kills, damage taken, and actions used) has been in for a while, and it always ran with the QUEUE feature available like the main game. This build adds a variant with QUEUE turned off entirely: every move and attack resolves the instant you confirm it, and drones react to each one individually rather than after a batch.

Same scoring, same checkpoints, but a different rhythm, closer to the very first stages before a player has queue habits built up. It shows up as its own tile in the difficulty picker and keeps a separate leaderboard from regular Challenge Mode, so the two are not competing over the same numbers.


Bug fixes

  • Android could confirm a limit break with one finger and no second tap. Godot's default touch handling sends both the real touch event stream and a synthetic emulated-mouse stream for the same finger gesture. The camera input script's press/drag/release state machine was processing both, so a single-finger drag onto a drone during a limit break preview could reach the confirm branch twice as fast as intended, sometimes skipping the second confirming tap a player expected to still need. Touchscreens now only feed the real touch stream into gameplay input; UI buttons and modals still get Godot's emulated mouse events directly and are unaffected.
  • The game rendered pinned to the top-left corner inside some embedded web views, most visibly the Reddit app's fixed-size iframe. ConfigManager was writing the OS-reported window size back into Godot's own cached size on every resize, which is harmless on desktop but desyncs the canvas anywhere the embed never actually sends a native resize event. Window-size read and write are now gated to desktop only; fullscreen no longer auto-applies at boot off-desktop either, only through an explicit click, which is the one place a user gesture makes it safe to request.
  • Two tutorial lines in Chapter 1 Stage 2 had drifted past the text-length budget after an earlier pass swapped bare "AA"/"AG" text for inline icon images without re-checking sentence count. Condensed back to one sentence each.
  • Katyusha's Curtain Capacity skill description said "before lapsing"; changed to "before expiring", plainer and less likely to trip up non-native readers.

Miscellaneous

  • The gameplay-options info rows (Queue Mode, Auto-attack, Reflex Move, Debug Grid, Cancel Pause) now show their hint text on hover for mouse users, not just on tap. The row label is an unstyled button with no visible chrome, so nothing about it looked clickable to a desktop player who was not already exploring by clicking things.
  • Fullscreen now defaults on for new desktop installs.
  • Cleaned up an unrelated bit of repo housekeeping: the Reddit app's folder had picked up its own independent .git at some point, most likely from the Devvit scaffolding tool, and every commit made inside it for the past several days had been landing there instead of in the main repository, with no remote backing it up. Folded that history back into the main repo so it is one project again instead of two silently diverging ones.
  • GDScript test suite: 962/962 passing.
  • Python solver test suite: 528/528 passing (17 skipped).
  • Build 85 shipped to Steam (demo and full).

Most of what shipped this round is the game being more honest about state it already had before letting the player commit to something big. The limit break confirmation was already proven UI for one ability; extending it to all three was mostly the discipline of not letting the "it worked fine before" reasoning excuse the other two from the same scrutiny. The start tile drag is the same idea pointed at the game's own decisions: the auto-pick was never wrong, exactly, just unaccountable, and now the player gets the last word on it.