Skip to content

Dev Log: 2026-05-29

Branch: rc/v0.1.3-dev


Achievement system

A full achievement system landed across several commits. Achievements are defined in a data file and tracked by AchievementManager against named stats. Three types are supported: cumulative (e.g. total drones killed), level-based (unit reaches a given level), and chapter-completion. Newly unlocked achievements surface on the stage result screen and are viewable in a dedicated dialog. Steam achievements are wired up via the Steamworks SDK: the achievement definitions carry a Steam key and AchievementManager calls the Steamworks API on unlock.

A progress display bug found during playtesting was fixed: the cumulative and level counters were showing raw stat values (e.g. 7/1 when the player had killed seven drones but the threshold was one). Both counters are now capped at the threshold, so progress always reads at most N/N.

A standalone Python package (steam_achievement_bulk_upload) was extracted from the local upload script and wired into the tooling so achievement metadata can be pushed to Steamworks without the Godot editor running.

AA/AG/both icons on drone sprites

Each combat drone now shows a small badge icon in its lower-right corner indicating its target type: an upward arrow for anti-air, a downward arrow for anti-ground, a diamond for all-targets. The badges are generated by ui_gen.py as 14x14 pixel-art sprites with a 1px black shape outline.

In a follow-up pass the badge colors were aligned to the range overlay colors: the anti-air badge now uses the same magenta-pink as the AA range fill, the anti-ground badge uses the reddish-orange of the AG range fill, and the all-targets badge uses the all-range red. This creates a direct visual link between the corner icon and the colored zone the drone projects on the map. The Canvas.shape_outline() helper was added to canvas.py to support the pixel-accurate outline expansion.

Tutorial text in Chapter 1 stages 2 and 4 was updated to introduce the icon and connect it to the range color.

Per-cell AG/AA/both range coloring

The threat-range overlay now colors each cell by which unit types it threatens rather than using a single color for all covered cells. Cells threatened only by anti-ground drones show orange, anti-air-only cells show pink, and cells threatened by both show red. This makes it possible to read the full threat map without inspecting individual drones.

When a unit is selected, the coloring switches to that unit's mobility class so the overlay reflects only the threats relevant to the active unit.

Range stays visible after drone inspect

After closing a drone's info panel, its range overlay now remains on screen so the player can study it unobstructed by the dialog. Tapping anywhere (empty cell, a unit, another drone) dismisses the locked range and returns to the default all-drones view. This was a frequent request from playtesting: the info panel and the range it was covering were the two things players most needed to see at the same time.

Tutorial text in stage 1 was updated to teach the behavior on first contact with the long-press inspect.

Limit Break button in unit detail dialog

The unit detail/inspect panel now shows a Limit Break button at the very top of the dialog (above the scroll area) when the selected unit's limit gauge is full. Tapping it closes the panel and enters limit break targeting mode directly. This addresses a common mobile friction point: players were accidentally opening the info panel instead of the HUD limit break button, then having to close it manually before the HUD button became reachable. The button uses the existing limit_break_requested signal path so no new stage-controller logic was needed.

The German translation of "Limit Break" was corrected across 13 strings: the previous translation "Limitbruch" is not conventional German; the correct form is the English loanword "Limit Break", which is standard in German-speaking game communities.

View action log on web

On web builds the "Export Action Log" button (stage result screen) is replaced by "View Action Log", which shows the JSON in a read-only text overlay with a copy-to-clipboard button. File saving is not available on web, so the native save dialog was not a usable option. Desktop builds are unchanged.

Smoother animations

Unit sprites now face the direction of travel before a projectile fires. A turn_to_face() method was added to unit.gd, which plays a brief rotation to the correct cardinal direction. The projectile table in stage_controller.gd was extended to cover all typed drone variants (interceptor_ag, interceptor_aa, cruiser_ag, cruiser_aa, guard_tower_ag, guard_tower_aa) that were previously falling through to a default.

Map border extension

The playable grid is now visually extended left and right with ghost terrain columns so the background does not cut off abruptly at the playfield edge when the camera is zoomed out. Ten ghost columns are stamped on each side using the edge terrain type. The extension is generated at stage load time and requires no changes to stage data.

Drag-and-drop cooldown

A 1-second cooldown was added after a drag-to-route gesture ends. During the cooldown the tap-to-confirm shortcut is suppressed so players can lift their finger after a drag without accidentally committing the route before reviewing it. The timestamp is stored on StageController and checked by both confirm paths.

Compact pause and options menus

The pause menu and options menu were slimmed down. Redundant spacing was removed and several controls were tightened. The options menu layout was simplified from a multi-column grid to a single-column flow that works better at both desktop and mobile viewport sizes.

Multi-reflex-attack bug fix

A bug caused a unit to fire multiple reflex counter-attacks against the same drone in a single reactive turn when the drone moved through several cells in range. The fix tracks which drones have already been countered within a single player action and skips repeat triggers. Regression guard tests were added to test_reflex_counter.gd and solver tick.py was updated to match.

Text clipping fixes

Two text clipping issues found on low-resolution test devices were fixed:

  • Achievement grid tiles were overflowing their cells at 5 columns; the grid was reduced to 4 columns and achievement name labels got ellipsis overflow handling.
  • The stage objective banner was clipping the bottom of descender glyphs at 26px font size. Vertical padding was increased and the label received a custom_minimum_size height to guarantee enough layout space.

Crash fix: freed drone reference

GridManager.get_occupant was crashing when a drone was freed (via queue_free) while still referenced in the _occupied dictionary. The fix adds an is_instance_valid check that auto-evicts stale entries on read, so the dictionary self-heals without requiring callers to manually unregister every destroyed node.

HUD button sizing

HUD route-control buttons (Confirm, Queue, Cancel) now size themselves from actual font metrics rather than hardcoded pixel widths. A _btn_natural_w() helper measures the button text plus icon and pads to a minimum. This fixed the German "Überspringen" skip button being clipped at the right edge in the cutscene player, and ensures button widths remain correct if font or text changes later.

Minor

  • Solver carry-state JSONs for Chapters 1 and 2 were refreshed after the reflex-attack fix changed outcome XP on several stages.
  • Translation overlays for all three locales were audited and missing or outdated keys were filled in.
  • Doc structure was updated to reflect the confirmed six-chapter scope.