Files
square-defense/docs/superpowers/plans/2026-04-17-phase3-k1-test-harness-completion.md
2026-06-16 10:57:01 +03:00

8.9 KiB
Raw Permalink Blame History

Phase 3.K.1 — Completion Report

Date completed: 2026-04-17 Source plan: 2026-04-17-phase3-k1-test-harness.md Source spec: ../specs/2026-04-17-phase3-k1-test-harness-design.md Execution mode: subagent-driven-development (fresh subagent per task, two-stage review).

Status

  • Code: Shipped. All 11 tasks complete.
  • Automated verification: ./gradlew core:test → 58/58 green. ./gradlew :core:build :lwjgl3:build → BUILD SUCCESSFUL.
  • Manual smoke test: Pending user — see checklist below.
  • Git: project is not a git repository; no commits were made. All work lives in the working tree.

What was delivered

New files (10)

Production (1):

  • core/src/main/java/ru/project/tower/GameContext.java — composition-root holder for shared persistent-state services. public final fields for PlayerStats, PlayerAbilities, GameState, TalentTree, and two Preferences refs.

Tests (9):

  • core/src/test/java/ru/project/tower/HarnessSanityTest.java
  • core/src/test/java/ru/project/tower/FakePreferences.java — in-memory Preferences impl, reused by every downstream test.
  • core/src/test/java/ru/project/tower/FakePreferencesTest.java
  • core/src/test/java/ru/project/tower/PlayerStatsTest.java
  • core/src/test/java/ru/project/tower/PlayerAbilitiesTest.java
  • core/src/test/java/ru/project/tower/GameStateTest.java
  • core/src/test/java/ru/project/tower/TalentTreeTest.java
  • core/src/test/java/ru/project/tower/TalentDataTest.java
  • core/src/test/java/ru/project/tower/GameContextTest.java

Modified files (10)

Production classes (singletons → DI):

  • PlayerStats.java — public PlayerStats(Preferences) ctor, getInstance() deleted, instance field deleted, PREFS_NAME deleted, Gdx import deleted, preferences field made final.
  • PlayerAbilities.java — public PlayerAbilities(Preferences) ctor, getInstance() deleted, instance field deleted, PREFS_NAME deleted, preferences field made final. Gdx import kept (used by Gdx.app.error(...)).
  • GameState.java — ctor made public, getInstance() deleted, instance field deleted.
  • TalentTree.java — public TalentTree(PlayerStats) ctor, getInstance() deleted, instance field deleted, two internal PlayerStats.getInstance() calls replaced with injected stats field, talents/roots made final.

Composition root + screens:

  • Main.java — rewritten; builds GameContext from two Preferences, passes to MainScreen.
  • MainScreen.java — 2-arg ctor, 3 downstream new X(game)new X(game, ctx).
  • FirstScreen.java — 2-arg ctor, 3 new cached fields (ctx, gameState, talentTree), 4 cached services assigned at ctor top (closed an NPE window), 13 getInstance() call-sites replaced, internal new MainScreen(game)new MainScreen(game, ctx), 2 redundant local shadows removed.
  • AbilityShopScreen.java — 2-arg ctor, 2 getInstance() replacements, internal new MainScreen(game, ctx).
  • AbilitySelectionScreen.java — 2-arg ctor, 2 getInstance() replacements, internal new MainScreen(gameInstance, ctx) + new FirstScreen(gameInstance, ctx).
  • TalentTreeScreen.java — 2-arg ctor, 2 getInstance() replacements, internal new MainScreen(game, ctx).

Build:

  • core/build.gradletestImplementation 'org.junit.jupiter:junit-jupiter:5.10.2', testImplementation 'org.assertj:assertj-core:3.26.3', test { useJUnitPlatform() }.

Test counts (by class)

Test class Tests
HarnessSanityTest 1
FakePreferencesTest 7
PlayerStatsTest 14
PlayerAbilitiesTest 8
GameStateTest 6
TalentTreeTest 12
TalentDataTest 6
GameContextTest 4
Total 58

Invariants verified at completion

  • grep "getInstance(" core/src/main/java/ru/project/tower -r → 0 matches.
  • grep "private static \w+ instance;" core/src/main/java/ru/project/tower -r → 0 matches.
  • ./gradlew core:test → 58 passed, 0 failed.
  • ./gradlew :core:build :lwjgl3:build → BUILD SUCCESSFUL.

Manual smoke test — pending

Run lwjgl3/src/main/java/ru/project/tower/lwjgl3/Lwjgl3Launcher and verify:

  • Main menu renders with persisted currency + max-wave.
  • Talent Tree: purchased levels display, new purchase deducts currency, back returns to menu.
  • Ability Shop: owned abilities display correctly, new purchase (e.g., Shield) persists after a restart.
  • Ability Selection → Start Game → wave 1 plays, firing kills enemies, death returns to menu, currency reflects loot.
  • Mid-run shop flow: save/restore works (upgrade survives shop round-trip).
  • Close and relaunch: all persisted state (currency, max wave, unlocked abilities, talent levels) survives.

If any check fails, report back — most likely a missed call-site or a ctx threading bug; fix scope is minutes, not hours.

Carry-over items (non-blocking)

These were flagged by reviewers during K.1 execution but are either pre-existing (not regressions introduced by K.1) or are tiny cleanups better folded into the next spec. They do not block K.2.

Pre-existing design issues surfaced by the new test harness

  • PlayerStats.getRegenTalentLevel() sums only regen_1. Other aggregate getters sum two nodes. If a regen_2 node is added later, this getter silently reports the wrong value. Add a regen_2 node or a comment explaining the omission. (Flagged in Task 3 review.)
  • PlayerStats.incrementTalentNode(String) accepts any string, including typos. Phantom nodes are created in the in-memory talentLevels map but not persisted (saveData iterates TALENT_NODE_IDS). Consider a validation guard. (Flagged in Task 3 review.)
  • PlayerStats.resetData() Javadoc says "для тестирования" but the test suite doesn't call it — callers live elsewhere. Clarify the comment or remove the method. (Task 3 review.)
  • PlayerAbilities.resetData() clears everything, including freeze and explosion which default to true on construction. Intentional per the existing code, but the name implies "factory defaults" which it isn't. Rename or add a comment. (Task 4 review.)
  • GameState.resetGameState() doesn't reset spawnedBosses or the gameplay stat fields (money, squareHealth, bulletSpeed, etc.). If this method is the "start new run" boundary, that's a latent bug. (Task 5 review.)
  • FirstScreen.java field declarations near lines 429435 are non-final while the ctx field is final. Consistency drift inside one file. (Final review.)
  • Main.ctx is public non-final. Safe given libGDX Game.create() semantics, but public final would document intent. (Final review.)

Test-coverage gaps (not bugs)

  • TalentTreeTest.getStatBonus_* exercises only the damage_1 multiplier. A second multiplier (cooldown_1, health_1, etc.) would lock more of the formula. (Task 6 review.)
  • TalentDataTest.enum_whenKeystone_thenEnumMatches is a tautology (KEYSTONE != DAMAGE). Safe to delete. (Final review.)
  • PlayerAbilitiesTest.purchaseAbility_whenUnknownType_thenNoChange doesn't assert that the unknown key wasn't written to Preferences. (Task 4 review.)
  • PlayerAbilitiesTest.construct_whenPreferencesPrePopulated_thenHonoursStored seeds only freeze and shield — doesn't verify that absent explosion key falls through to the true default. (Task 4 review.)

FakePreferences polish

  • FakePreferences.put(Map<String,?>) bulk-put isn't guarded — values inserted through this path could be types no getter recognises (e.g., Double, Short). No test exercises this. (Task 2 review.)
  • FakePreferences.getFloat/getLong and the no-default single-arg getters have no tests. (Task 2 review.)

What's next

K.1 is the foundation. The remaining specs in the K-series each handle one subsystem extraction out of FirstScreen's god-class. Each follows the same brainstorm → spec → plan → subagent-driven-implementation cycle.

Spec Scope Dependency
K.2 BulletSystem extraction — bullet tick, bullet-vs-circle collision, homing/pierce/aoe/chain resolution K.1
K.3 SpawnController extraction — spawnCircle() cascade K.1
K.4 WaveController extraction — startNextWave / endWave / scaling K.1
K.5 EnemyBehaviour tests — tick() for HealerCircleData, DasherCircleData, SniperCircleData (and others) K.1 + Phase 3 A1/A2/A3
K.6 AbilitySystem extraction — cooldowns, activations, effects K.1
K.7 UpgradeSystem extraction — mid-run upgrade pick, money, apply-effect K.1

Recommended next step: brainstorm K.2 (BulletSystem) — highest-value extraction because bullet logic is central to the game, has the most flags (piercesLeft, homing, aoeRadius, chainJumps), and is the first place Phase 3's homing keystone + C2 chain work will land.

Phase 3 gameplay tasks (AJ from the parent Phase 3 plan) remain untouched and can proceed in parallel with K.2+ when desired.