atomic pair - purchasing couples them, so neither moves alone.
SkillData.Level is gone. It was an [Export] on a cached Resource: one
instance per process, so the level never persisted, leaked between save
slots, and made "unowned" indistinguishable from "level 1". Level now
comes from the Playthrough and is passed to ISkill.Initialize.
SkillManager reconciles from SkillSetChanged instead of being told by
each caller. The old shape was "record it, then tell SkillManager" as two
calls at four sites; AddSkill's early return when no player is registered
left the skill owned but inert. Reconciliation also tracks the level each
component was built at, so an upgrade rebuilds it rather than being
patched in from outside.
SkillUnlockerComponent collapses to one TryBuy - unlock and upgrade were
two methods each doing affordability, debit, level mutation and manager
notification in order.
The guard three components copy-pasted around Upgrades[Level - 1] - and
which BrickArmor omitted - now lives once in SkillComponentBase.
Hud reads coins from CoinsChanged instead of rebuilding translated
strings every frame. Lives still polls; it moves with the death path.
every level load left another handler attached to a freed HUD.
SpeedRunManager outlives the scene, so it kept calling them and each one
wrote to a disposed Label - thousands of ObjectDisposedException per
frame, growing with each load.
Latent all along: StartTimer was only ever called from StartNewGame, so
after a Continue the timer never ticked and the dead handlers never ran.
Calling StartTimer on Continue (needed for splits and best times) made
it visible.
Also stop the timer when leaving to the menu - StopTimer existed and had
no callers, so the run kept ticking in the main menu.
narrowest safe step: only level identity and progression move. Coins,
lives, skills and death stay entirely on GameStateStore and behave as
before, so nothing is tracked in two places.
GameManager addresses levels by stable LevelId via the catalogue;
LevelScenes is gone, so reordering levels no longer invalidates saves.
Every level entry goes through EnterLevel, which fixes LevelStarted never
firing on the first level or after Continue - ghost recording and splits
have been dead outside mid-campaign.
GoTo announces after a frame, once the incoming scene is actually
current. ChangeSceneToPacked is deferred, so the old code handed
GhostEventHandler the scene being torn down.
FinishLevel takes LevelTime? - GetCurrentLevelTime can legitimately be 0
and inventing a value is exactly the bad data LevelTime refuses.
Continue distinguishes absent from corrupt and refuses to overwrite a
corrupt save. Statistics move to the Profile. 69 tests.
slice 7 regression. level_village_2..5.tscn each reference their own
LevelResource for the death screen label, so pointing the resource back
at the scene forms a cycle Godot cannot resolve - "Parse Error: Busy"
and the level fails to load entirely.
ScenePath was a string for exactly this reason. Reverted, and the rot it
allowed (forest_1 aimed at a nonexistent scene) is now caught by
LevelCatalogue.Validate checking every path resolves, plus a repo-level
structural check - fixed by validation rather than by typing.
project.godot: restores the C# feature flag and dialogue_manager
uses_dotnet, which `godot --headless --import` strips on every run.
new progression.adapters project, no engine reference. Stores take a
directory path; Godot resolves user:// with GlobalizePath and passes it
in, which is what makes them testable at all - originally I'd written
this slice off as untestable.
version lives in a file envelope, not the snapshot, so the domain only
knows the current shape. A newer-build save reports Corrupt with a
reason instead of masquerading as "no save" and getting overwritten.
atomic write via temp + rename. One file per aggregate: saving a run
leaves the profile file byte-identical, asserted.
guard rail fixed - it was matching prose in doc comments, now anchored
to using-directives plus a project.assets.json check. 63 tests.
LevelResource gains a stable Id and swaps the string ScenePath for an
editor-tracked PackedScene. The old path field was never read and had
rotted - forest_1 pointed at level_forest_5.tscn, which doesn't exist,
and borrowed LEVEL_5_NAME so it would have displayed "VILLAGE - 5".
LevelCatalogue lists levels in campaign order and produces the domain's
Campaign; an authoring mistake fails at boot because Campaign.Of rejects
blank and duplicate ids. Not a domain port - nothing in the domain
resolves a level to a scene.
All 8 scenes now have a resource; campaign keeps the existing 6
(tutorial_1, village_1..5). village_6 and forest_1 are catalogued but
deliberately out of the campaign.
GameManager.LevelScenes stays until slice 10 rewrites GameManager.
- Fix double-execution bug in LevelStateHandler (coins/skills were committed twice per level)
- Fix DamageComponent to track multiple targets via HashSet instead of single node
- Fix HealthComponent: update health immediately, decouple from PlayerController via signal wiring in PlayerController
- Remove dead loop in SkillManager.RemoveSkill; simplify O(n²) throw skill loop
- Replace GetNode<T>(path) with .Instance across managers; add Instance to StatisticsManager/SpeedRunManager
- GameManager.GetPlayer() now uses EventBus.PlayerSpawned instead of scanning all scene nodes
- UIManager.UiStack: remove [Export], use private List<Control>
- PlayerState: extract DefaultLives constant, simplify CreateDefault()
* Add GridMovementAbility and PacXonGridInteractor for grid-based movement; integrate with PlayerController and PacXonLevel
* Add GhostMovementComponent and PacXonTrailComponent; integrate ghost movement and trail features in PacXonLevel
* Update main menu button focus and add new movement abilities; adjust player and ghost initialization in PacXonLevel