Initial commit

This commit is contained in:
2026-06-16 10:57:01 +03:00
commit 1cd5c8b03c
277 changed files with 42921 additions and 0 deletions
@@ -0,0 +1,127 @@
# 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.gradle``testImplementation '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.