Overview
This test plan covers the training grounds (Training Grounds):
- The teleport offer โ
TeleportMenu (what the player is asked, controls lock, confirm, decline, mash protection) and its keyboard handler, built on the shared ModalMenuInputComponent.
- The practice props โ
TrainingDummyComponent (heal after every hit, never die, held still), PropInteractableComponent (range, prompt, indicator) and DodgeCannonComponent (firing, hits, dodges, frozen time).
- The temporary loadout โ
TrainingLoadoutComponent, which enchants every weapon on entry, lends consumables and puts both back on leaving.
- The prop manager's tolerance of bad prop definitions โ
TutorialPropManagerComponent.
- Prop assets โ
TutorialPropFactory (icons for every stand item, every texture exists).
- Moving the player โ
RoomLoader.getEntrySpawn and the choice between createRoom and createTrainingRoom.
- The shipped data โ the
tutorialRoom in rooms.json, the two Old Sage definitions in friendlyNpcs.json and the notice board dialogues: props sit on open floor, every sign reads a real dialogue, every stand lends a real item with an icon, and both Sages point at valid entry points.
- The rule that only training grounds may have props, enforced by
WorldConfig.validate.
The training grounds and the Old Sage are deliberately separate things: the Sage is an Interactable NPC with a teleportOffer; the room is an ordinary room with a trainingGrounds flag. They are tested together here because the offer is only useful if the room it points at is valid.
Testing is split into automated unit tests (what the code must do) and manual gameplay tests (what the player must see). Every behaviour in the automated plan is enforced by a test in the repository โ the test class is named above each table.
Test Environment
- Unit testing: JUnit 5 and Mockito, run headlessly with the engine's
GameExtension. OpenGL is mocked, so the components are tested against plain entities. Time is driven by a mocked GameTime (getDeltaTime) and the teleport menu takes an injectable clock, so nothing sleeps. RoomLoaderTest uses Mockito's static mocking to check which RoomFactory method is called.
- Game testing: Desktop application.
- Movement:
WASD โ Dash: SPACE โ Interact / read a board / take an item: E โ Attack: J โ Heavy attack: K โ Weapons: 1 2 3 โ Potions: TAB to pick, Q to use โ Menu: W S / arrows, ENTER / SPACE / E confirm, ESC / Q stay โ Debug terminal: F1
- Terminal command:
room goto tutorialRoom # move straight into the training grounds
- Shipped content: the
tutorialRoom (60 ร 46 tiles) with 5 notice boards, 6 dummies, 5 archery targets, 7 supply stands and 3 cannons; the Old Sage in the hub (tutorialGuide) and in the room (trainingGuide); dialogues tutorial_guide, training_guide and tutorial_sign_controls / _melee / _archery / _supplies / _dodge.
- Tuned values as shipped: confirm is ignored for 400 ms after the menu opens; a cannon fires every 2.4 s, staggered by 0.8 s, and an orb travels 16 tiles at 3 units per second; an orb within 0.3 units of the player is a hit, and one that passes within 1.1 units without touching is a dodge; a dummy has 9999 health and is healed to full after every hit; a prop can be used within 1.3 units, a Sage within 1.5.
Run the automated tests with:
cd source
./gradlew :core:test
Coverage is written to source/core/build/reports/jacoco/test/html/index.html by:
cd source
./gradlew :core:jacocoTestReport
Coverage
Last verified run: 22 Oct 2026 on main (705de3df, containing PR #352) โ 2725 tests, 0 failures, 0 skipped project-wide, of which 78 tests across 10 classes were added for the training grounds.

Reference: SonarCloud project overview ยท SonarCloud analysis of PR #352 (0 open issues, 0.0% duplication on new code at the time of merging)
The badges above are project-wide. The figures below are the training grounds code only, from a local JaCoCo run of the revision above.
Per-file coverage
| File |
Lines |
Line coverage |
Branch coverage |
Method coverage |
components/tutorial/TrainingDummyComponent.java |
15 |
100.0% |
50.0% |
100.0% |
components/tutorial/TrainingLoadoutComponent.java |
56 |
96.4% |
78.1% |
100.0% |
components/tutorial/DodgeCannonComponent.java |
60 |
86.7% |
81.2% |
75.0% |
components/tutorial/PropInteractableComponent.java |
20 |
90.0% |
100.0% |
85.7% |
components/maingame/TeleportMenu.java |
56 |
91.1% |
96.2% |
86.7% |
components/maingame/ModalMenuInputComponent.java |
25 |
100.0% |
84.6% |
100.0% |
components/maingame/ModalMenuDisplay.java (shared with Hecate's menu) |
61 |
96.7% |
85.7% |
84.6% |
components/maingame/KeyboardTeleportMenuInputComponent.java |
4 |
50.0% |
โ |
50.0% |
components/rooms/configs/PropSpawnConfig.java |
9 |
100.0% |
โ |
100.0% |
components/tutorial/TutorialPropManagerComponent.java |
119 |
24.4% |
17.6% |
40.9% |
entities/factories/TutorialPropFactory.java |
42 |
23.8% |
50.0% |
38.5% |
components/maingame/TeleportMenuDisplay.java |
27 |
0.0% |
0.0% |
0.0% |
components/tutorial/DamagePopupDisplay.java |
29 |
0.0% |
0.0% |
0.0% |
components/tutorial/FloatingIconComponent.java |
18 |
0.0% |
0.0% |
0.0% |
Existing files that were changed. These figures are for the whole file, so they include code this feature did not touch:
| File |
Lines |
Line coverage |
Branch coverage |
Method coverage |
components/rooms/configs/WorldConfig.java (new: props only in training grounds) |
42 |
95.2% |
77.3% |
100.0% |
entities/configs/InteractableNpcConfigs.java (new: teleportOffer validation) |
33 |
100.0% |
77.8% |
100.0% |
entities/configs/InteractableNpcConfig.java (new: TeleportOffer) |
17 |
100.0% |
75.0% |
100.0% |
components/maingame/AbilityMenuDisplay.java (now a ModalMenuDisplay) |
58 |
94.8% |
75.0% |
100.0% |
rooms/RoomSelector.java (new: teleport, propInteract) |
89 |
59.6% |
50.0% |
60.0% |
rooms/RoomLoader.java (new: getEntrySpawn, buildRoom) |
56 |
32.1% |
36.8% |
38.5% |
entities/factories/RoomFactory.java (new: createTrainingRoom) |
55 |
1.8% |
0.0% |
10.0% |
Aggregate across the 9 logic classes (the first nine rows above; JaCoCo, source/core/build/reports/jacoco/test/html/index.html):
| Counter |
Covered |
Missed |
Coverage |
| Instructions |
1115 |
57 |
95.1% |
| Branches |
94 |
17 |
84.7% |
| Lines |
285 |
21 |
93.1% |
| Methods |
65 |
8 |
89.0% |
| Classes |
11 |
0 |
100.0% |
Aggregate across all 14 training grounds classes, including the prop manager, the factory and the three classes that draw with OpenGL:
| Counter |
Covered |
Missed |
Coverage |
| Instructions |
1349 |
945 |
58.8% |
| Branches |
107 |
92 |
53.8% |
| Lines |
324 |
217 |
59.9% |
| Methods |
79 |
45 |
63.7% |
| Classes |
13 |
3 |
81.2% |
The logic is at about 93% line coverage. The overall figure is lower because TutorialPropManagerComponent, TutorialPropFactory and the three display classes need a real terrain, textures or an OpenGL context, and are covered by the manual plan below. The uncovered lines in the logic classes are listed under Known coverage gaps.
Tests per class
| Test class |
Tests |
TrainingGroundsConfigTest |
15 |
TeleportMenuTest |
12 |
TrainingLoadoutComponentTest |
12 |
DodgeCannonComponentTest |
8 |
KeyboardTeleportMenuInputComponentTest |
6 |
PropInteractableComponentTest |
6 |
RoomLoaderTest (7 in the class; 6 added for this feature) |
6 |
TrainingDummyComponentTest |
5 |
TutorialPropManagerComponentTest |
4 |
TutorialPropFactoryTest |
4 |
| Total added |
78 |
Automated Unit Test Plan
The teleport offer
TeleportMenuTest
The menu holds no UI or libGDX state, so the whole flow โ open, choose, confirm, close โ is tested directly with a player that has real PlayerActions and a hand-moved clock.
| Test |
Purpose |
Expected Result |
shouldStartClosed |
Build the menu. |
Closed, no offer, controls not locked. |
shouldOpenWhenAnNpcWithAnOfferFinishes |
Send INTERACTION_FINISHED for an NPC whose config has a teleportOffer. |
The menu opens on that offer with going ahead highlighted, and teleportMenuOpened fires once. |
shouldIgnoreNpcsWithoutAnOffer |
Finish an NPC with no offer, a null NPC and a bare entity. |
The menu stays shut and no event fires. |
shouldLockControlsWhileOpenAndReleaseThemOnClose |
Open, then close. |
Controls are locked while open and released on close, with opened then closed fired. |
shouldNotOpenTwice |
Open a second offer while one is showing. |
Returns false; only one opened event. |
shouldRequestTheTeleportWhenConfirmed |
Confirm the first row after the grace period. |
teleportToRoom is sent with the offer's room and entry point, the menu closes, and controls are already released so the player arrives free to move. |
shouldStayPutWhenTheDeclineRowIsConfirmed |
Move to the second row and confirm. |
No teleport request; the menu closes. |
shouldIgnoreAConfirmMashedStraightAfterOpening |
Confirm immediately after opening, then after 400 ms. |
The early press does nothing โ the menu stays open and nothing is sent โ and the later press goes ahead. This is the guard against a press meant for the Sage's last line also accepting the offer. |
shouldWrapTheSelectionAtBothEnds |
Move down twice and up once. |
The highlight wraps: row 2, row 1, row 2. |
shouldHighlightGoingAheadAgainOnEveryOpen |
Move to the decline row, close, reopen. |
Going ahead is highlighted again. |
shouldDoNothingWhileShut |
Move, confirm and close on a shut menu. |
Nothing happens, no events, no request. |
shouldCloseWhenThePlayerDies |
Trigger entityDied with the menu open. |
It closes and the control lock is released, so dying cannot strand the lock. |
KeyboardTeleportMenuInputComponentTest
The shared ModalMenuInputComponent is tested through the teleport menu's keyboard handler. Its other subclass, for Hecate's menu, has its own existing tests.
| Test |
Purpose |
Expected Result |
shouldSitAboveTheUiStageAndBelowDialogue |
Compare priorities. |
Above the debug terminal and UI stage (10) and below dialogue input (20), so a dialogue always wins and the menu is modal over everything else. |
shouldIgnoreEverythingWhileClosed |
Send keys, a click and a scroll with the menu shut. |
Nothing is consumed. |
shouldSwallowEverythingWhileOpen |
Send A, D, releases, typed characters, clicks, drags and a scroll while open. |
All are consumed and the menu stays open, so nothing walks the player around behind it. |
shouldMoveTheSelectionWithArrowsAndWasd |
Press DOWN, UP, S, W. |
The highlight moves between the two rows each time. |
shouldConfirmWithEnterSpaceOrE |
Open and press ENTER, NUMPAD_ENTER, SPACE, then E. |
Each requests the teleport and closes the menu. |
shouldLeaveWithEscapeOrQ |
Open and press ESC, then Q. |
Each closes the menu without a teleport request. |
The practice props
TrainingDummyComponentTest
| Test |
Purpose |
Expected Result |
shouldBeToppedUpAfterEveryHit |
Deal 25 damage. |
Health is back at its maximum and the dummy is not dead. |
shouldReportEachHitWithItsDamageAndRunningTotal |
Deal 25, then 40. |
trainingTargetHit fires with (label, 25, 1) then (label, 40, 2), and getHits() is 2. |
shouldNeverDieNoMatterHowHardItIsHit |
Deal one less than its maximum health five times. |
entityDied never fires and health stays at its maximum. |
shouldNotCountZeroDamage |
Deal 0 damage. |
No hit counted, no event. |
shouldBeHeldStillEveryFrame |
Update a dummy that has a physics body. |
The body's velocity is set to zero. A dummy has to be a dynamic body for weapon hitboxes to register on it, so this stops knockback sliding it across the room. |
PropInteractableComponentTest
| Test |
Purpose |
Expected Result |
shouldBeInRangeOnlyWithinItsRange |
Test a player 1.5 and 2.5 units away with a range of 2. |
In range, then out of range. |
shouldMeasureDistanceBetweenCentres |
A player at (3, 4) from a prop at the origin. |
Distance is 5. |
shouldRunItsActionWithThePlayer |
Interact. |
The action runs once with that player. |
shouldExposeItsPrompt |
Read the prompt. |
It is the text it was built with. |
shouldOnlyAnnounceTheIndicatorWhenItChanges |
Show twice, hide twice. |
INDICATOR_SHOWN then INDICATOR_HIDDEN, each once. |
shouldRefuseMissingPromptOrAction |
Build with a null prompt, then a null action. |
Each throws NullPointerException. |
DodgeCannonComponentTest
Cannons are built with a plain entity as their orb, so no art is needed, and time is stepped 0.1 s at a time.
| Test |
Purpose |
Expected Result |
shouldFireImmediatelyWhenThereIsNoDelay |
Update once with no delay. |
One orb exists. |
shouldWaitOutItsDelayBeforeTheFirstShot |
Give it a 0.5 s delay and step. |
Nothing before 0.5 s, one orb just after. |
shouldFireAgainEveryInterval |
One-second interval over 2.5 s. |
Three orbs. |
shouldSpawnOrbsAtTheCannonsMouthAndMoveThemDown |
Fire one and step. |
It starts centred under the cannon and its y falls. |
shouldReportAHitWhenAnOrbReachesThePlayer |
Put the player dead centre in the lane. |
One dodgeResult(false). |
shouldReportADodgeWhenAnOrbPassesCloseWithoutTouching |
Put the player beside the lane, close enough to be dodging but not clipped. |
One dodgeResult(true) once the orb has travelled its range. |
shouldIgnoreOrbsThatNeverComeNearThePlayer |
Put the player far from the lane. |
No result at all โ wandering elsewhere in the room is not a dodge. |
shouldNotFireWhileTimeIsStopped |
Use a clock with a zero delta. |
No orbs, which is how a dialogue freezes the cannons. |
TutorialPropManagerComponentTest
Spawning a real prop needs terrain, so these cover how the manager copes with bad definitions, which is the path that must never stop a room from loading.
| Test |
Purpose |
Expected Result |
shouldSkipAPropWithNoTypeInsteadOfFailingTheRoom |
Spawn a prop with no type. |
Skipped without an exception. |
shouldSkipASignWithNoDialogue |
Spawn a sign with no dialogueId. |
Skipped without an exception. |
shouldSkipASupplyStandForAnUnknownItem |
Spawn stands for an unknown item and for no item. |
Both skipped without an exception. |
shouldFindNothingToUseInARoomWithNoProps |
Ask a propless room for the nearest prop. |
null. |
TutorialPropFactoryTest
| Test |
Purpose |
Expected Result |
iconPathsAreNamedAfterTheItemId |
Ask for the icon path of HEALTH_POTION. |
images/tutorial/icons/health_potion.png. |
everyStandItemHasAnIconThatIsLoadedWithTheRoom |
Check the seven stand items. |
Each icon is in the room's texture list. |
everyPropTextureExistsOnDisk |
Resolve every prop texture. |
Every file exists, so a missing sprite fails the build rather than a player. |
aStandForAnItemWithoutAnIconIsRefused |
Build a stand for a gold coin. |
IllegalArgumentException. |
The loadout
TrainingLoadoutComponentTest
A player with real WeaponUpgradeComponent and InventoryComponent enters and leaves a room that lends a health potion and a freeze bomb.
| Test |
Purpose |
Expected Result |
shouldUpgradeEveryWeaponOnEntry |
Enter the room. |
The sword, knife and bow are all upgraded. |
shouldExplainTheEnchantmentOnEntry |
Enter the room. |
One status message, and it mentions heavy attacks. |
shouldTakeTheUpgradesBackOnLeaving |
Enter, then dispose the room. |
No weapon is upgraded. |
shouldKeepUpgradesThePlayerAlreadyBought |
Own a sword upgrade, enter, leave. |
The sword is still upgraded; the bow is not. |
shouldLendOneCopyAtATime |
Lend a potion twice. |
The first succeeds, the second is refused, and the player holds one. |
shouldLendAgainOnceTheLentCopyIsUsed |
Lend, use it, lend again. |
The second lend succeeds. |
shouldTakeLentItemsBackOnLeaving |
Lend a potion and a bomb, then leave. |
Both counts are back to zero. |
shouldLeavePotionsThePlayerBroughtInAlone |
Arrive with two potions, lend one, leave. |
They hold three while inside and exactly two after. |
shouldNeverTakeMoreThanWasLent |
Arrive with one potion, drink it inside, leave. |
They end with none; nothing is taken that they did not receive. |
shouldOnlyLendWhatTheRoomOffers |
Try to lend a shield. |
Refused, because no stand in the room offers it. |
shouldNotLendBeforeThePlayerHasEntered |
Lend before RoomCreated. |
Refused. |
shouldRestoreOnlyOnce |
Leave, add a potion, dispose the component again. |
The second dispose changes nothing. |
Moving the player and choosing the factory
RoomLoaderTest
Only the six tests added for this feature are listed. The seventh, shouldValidateWorldOnContructor, was already in the class.
| Test |
Purpose |
Expected Result |
shouldLandATeleportAtTheNamedEntryPoint |
Ask for the entry point gate of room yard. |
Its tile position. |
shouldRefuseATeleportToAnUnknownEntryPoint |
Ask for an entry point that does not exist. |
IllegalArgumentException. |
shouldRefuseATeleportToAnUnknownRoom |
Ask for a room that does not exist. |
IllegalArgumentException. |
shouldBuildTrainingGroundsWithTheTrainingFactory |
Load a room flagged trainingGrounds. |
createTrainingRoom is used and createRoom is never called. |
shouldBuildEveryOtherRoomWithTheUsualFactory |
Load an ordinary room twice. |
createRoom is used and createTrainingRoom is never called. |
shouldChooseTheFactoryForTheStartingRoomToo |
Load the starting room. |
It goes through the same choice. |
Shipped Data
TrainingGroundsConfigTest
These load the real shipped JSON, so a broken room, prop or Sage fails the build rather than a player.
| Test |
Purpose |
Expected Result |
worldStillValidatesWithTheRoomAdded |
Validate the world and the NPC definitions. |
Neither throws. |
roomIsAPracticeSpaceWithNoEnemiesOrExits |
Inspect tutorialRoom. |
Flagged trainingGrounds, no enemies (they would block clearing and drop loot), no exits, and dungeonId is null so no dungeon timer starts. |
otherRoomsAreNotTrainingGrounds |
Inspect every other room. |
None is flagged and none has props. |
mapGridMatchesItsDeclaredSize |
Compare the grid to mapWidth and mapHeight. |
Every row is the declared width and there are the declared number of rows. |
bothSagesOfferToTeleportToEachOther |
Read both Sages' offers. |
The hub Sage goes to tutorialRoom, the training Sage to selection, and each entry point exists. |
trainingSageStandsInTheRoomAndHubSageStaysInTheHub |
Look at the NPC spawns. |
The training Sage is only in the room and the hub Sage only in the hub. |
arrivalsLandOnFloorInBothDirections |
Apply the spawn offset (+2, โ2) to fromSage and fromTutorial. |
Both land on floor, not in a wall. |
everyPropAndNpcStandsOnOpenFloorInsideTheMap |
Check every prop and NPC position. |
Inside the map, on floor and not on a wall or crate, and no two on one tile. |
roomTeachesEveryMechanic |
Count props by type. |
There is at least one of every PropType. |
everySignReadsARealDialogue |
Load each sign's dialogue. |
It loads, has lines, every speaker is defined and every portrait exists. |
everySupplyStandLendsARealConsumableWithAnIcon |
Check each stand. |
A real item with an icon on disk, no duplicates, and both freeze bombs and burn vials are on offer. |
cannonsAreConfiguredToFireAtAReadableRate |
Check each cannon. |
At least one second between shots, a positive range and a non-negative delay. |
trainingGuideDialogueLoads |
Load the training Sage's dialogue. |
It loads and has lines. |
propsAreOnlyAllowedInTrainingGrounds |
Give the hub the room's props, validate, then flag the hub. |
Validation fails, then passes once the hub is a training ground. |
teleportOffersNeedADestination |
Validate an NPC with no offer, an empty offer, a room only, then room and entry point. |
Valid, rejected, rejected, valid. |
Manual Gameplay Test Plan
Setup for every test below: run the game (cd source && ./gradlew :desktop:run), start a run and talk to the Old Sage in the hub to reach the training grounds. To skip the dialogue, open the debug terminal (F1) and run room goto tutorialRoom.
Getting there and back
| Test |
Steps |
Expected Result |
| Offer appears |
In the hub press E next to the Old Sage and advance to the last line. |
After the final line a panel titled Training Grounds asks the question, with two rows, the first highlighted, and a key hint along the bottom. |
| Mashing is safe |
Mash SPACE through the whole dialogue. |
The menu opens but is not confirmed by the presses that finished the dialogue. |
| Choosing |
Press S then W. |
The highlight moves and the text of the highlighted row darkens. |
| Stay |
Choose Not right now, or press ESC. |
The panel closes, you stay in the hub and can move again. |
| Controls locked |
With the panel open, press A, D, J and I. |
The player does not move, attack or open the inventory behind the panel. |
| Go |
Choose Take me to the training grounds. |
The room changes, the title Training Grounds shows, you can move straight away and you stand near the second Old Sage. |
| Enchantment notice |
Look at the top of the screen on arrival. |
A message says weapons are enchanted and heavy attacks work. |
| Music |
Listen after arriving. |
The calm hub music keeps playing; the fight music never starts. |
| Return |
Press E at the training Sage, advance the dialogue and choose Return to the village. |
You arrive in the hub beside the original Sage and can move. |
| Repeat |
Go there and back a second time. |
Same result, no stuck controls and no duplicated props. |
Notice boards
| Test |
Steps |
Expected Result |
| Marker and prompt |
Walk up to each board. |
A yellow marker bobs above it and the top right shows Press E โ Read and the board's name. Walking away hides both. |
| Reading |
Press E at each of the five boards. |
A dialogue opens with Notice Board as the speaker and the board's portrait, and the world freezes until it ends. |
| Content |
Read all five. |
They cover controls, melee, archery, supplies and dodging, and the keys named in them match the game. |
| Sage wins |
Stand where both a board and the Sage are in range. |
E talks to the Sage, not the board. |
| No double trigger |
Press E once at a board. |
Exactly one dialogue opens. |
Melee
| Test |
Steps |
Expected Result |
| Damage numbers |
Face a dummy and press J. |
A number floats up from the dummy and the status line shows the damage and hit count. |
| Never dies |
Hit a dummy 30 times. |
It never dies or disappears. |
| Held still |
Push into a dummy, and hit it while standing next to it. |
It does not slide. |
| Heavy attack |
Press K with the sword, the knife and the bow. |
The sword spins all the way round, the knife lands a three-hit flurry and the bow fires three arrows. Each works even though you have not bought the upgrade. |
| Weapon swap |
Press 1, 2 and 3. |
The weapon changes and each hits for a different amount. |
| Dummies in the supplies room |
Hit a test dummy. |
Same behaviour; the label is Test Dummy. |
Archery
| Test |
Steps |
Expected Result |
| Near target |
Press 3, stand on the red line, face a near target and press J. |
The arrow hits, a number floats up and the status line says Near Target. |
| Far target |
Aim at a far target. |
The arrow reaches it. |
| Spread |
Press K facing a target. |
Three arrows fire in a spread. |
| Blocking |
Line up a far target directly behind a near one. |
The arrow stops on the near target. |
| Walls |
Shoot at a box or a sign. |
The arrow disappears; it does not pass through. |
Supplies
| Test |
Steps |
Expected Result |
| Icons |
Look at the seven stands. |
Each shows a clear icon of its item, floating above the pedestal, readable from normal play distance. |
| Marker |
Stand at a stand. |
The marker is above the icon, not over it. Prompt reads Press E โ Take and the item name. |
| Take |
Press E. |
A message says you took one, and it is in your inventory (and the consumable bar if a slot is free). |
| Only one |
Press E again. |
A message says you already hold one; nothing is added. |
| Use and retake |
Use it with TAB and Q, then take another. |
You can take another once the lent one is used. |
| Effects |
Take and use each of the seven. |
Healing, shield, speed and strength work. A freeze bomb freezes, and a burn vial burns, the test dummies on screen. A magnet potion has its normal effect. |
| Nothing leaks |
Take a potion, leave through the Sage, then check the inventory. |
The lent potion is gone. Potions you owned before are still there. |
| Brought-in potions |
Arrive with a health potion, take one, leave. |
You are back to the one you owned. |
Dodge range
| Test |
Steps |
Expected Result |
| Orbs fire |
Walk to the top right. |
Three cannons fire purple orbs down their lanes, staggered. |
| Hit |
Stand in a lane. |
The orb disappears on touching you, the status line says you were clipped, and your health does not change. |
| Dodge |
Dash (SPACE) across a lane as an orb arrives. |
The status line says Dodged and the count goes up. |
| Far away |
Stay well away from the lanes. |
No dodge is counted. |
| Frozen by dialogue |
Read the Dodge Board while orbs are in flight. |
The orbs stop while the dialogue is open and carry on afterwards. |
Leaving and restoring
| Test |
Steps |
Expected Result |
| Upgrades restored |
Before going, check whether a weapon is upgraded (heavy attack works), then go there and back. |
Weapons that were not upgraded can no longer heavy attack, and ones you had bought still can. |
| Terminal entry |
Run room goto tutorialRoom from the hub and from a dungeon room. |
You arrive in the training grounds with the same enchantment, and leaving restores it. |
Regression: Hecate's menu
Hecate's menu now shares its input and display code with the teleport menu.
| Test |
Steps |
Expected Result |
| Looks the same |
Press E at Hecate and finish her dialogue. |
The same cream panel, title, two ability rows with the current one marked carried, and a key hint. |
| Keys |
Use W / S, ENTER and ESC. |
Choose, attune and leave work as before, and nothing walks the player behind it. |
Known Coverage Gaps
Everything uncovered in the logic classes is listed here. Nothing below is a known defect; each is either untestable without terrain, textures or OpenGL, or a path no shipped content reaches.
| Location |
Uncovered |
Why |
TeleportMenuDisplay, DamagePopupDisplay, FloatingIconComponent |
The whole classes (0%) |
They create scene2d actors, read textures and draw with the batch, which need a real OpenGL context; GameExtension mocks GL. Covered by the manual plan above. The behaviour they depend on is tested through TeleportMenu and TrainingDummyComponent. |
TutorialPropManagerComponent |
Spawning, build*, lendItem, onDodgeResult, onInteract, update, updatePrompt, dispose (24%) |
Spawning a prop needs a TerrainComponent and loaded textures. Only the tolerance of bad definitions is unit tested. The rest is covered by manual tests for boards, stands, dummies and cannons. |
TutorialPropFactory |
Building each prop (the create* methods) |
They create physics bodies and read textures from the ResourceService. The icon naming, the texture list and the refusal of a stand without an icon are tested. |
RoomFactory.createTrainingRoom |
The whole method |
It calls createRoom, which builds terrain and needs OpenGL. Which factory is chosen is tested in RoomLoaderTest; the room as a whole is covered by manual testing. |
RoomSelector.teleport |
The method and its scheduling |
RoomSelector is built around a real room and entity service. The entry point lookup it uses is tested in RoomLoaderTest and the request is tested at the menu. |
TeleportMenu(Entity) |
The public constructor that uses the real clock |
Tests use the package-private constructor with a hand-moved clock. |
TeleportMenu.dispose |
Closing a menu while the entity is disposed |
Defensive; close itself is tested. |
KeyboardTeleportMenuInputComponent(TeleportMenu) |
The constructor that takes a menu |
Tests use the no-argument constructor, which finds the menu on the entity, as the game does. |
ModalMenuDisplay |
draw and getZIndex |
Drawn by the stage; the rest of the class is covered through Hecate's menu tests. |
DodgeCannonComponent |
The public constructor and dispose |
Tests inject an orb with no art. Disposing live orbs with the room is checked manually. |
DodgeCannonComponent.update |
The "no time source" branch |
Defensive; tests always register one. |
PropInteractableComponent |
The constructor that uses the default range |
Tests pass a range. |
TrainingLoadoutComponent |
The "already applied" guard on a repeated RoomCreated, and the "could not remove" guard |
Defensive; neither is reachable from the shipped room. |
Areas Not Covered by Automated Tests
- Visual correctness. Nothing asserts a pixel. Whether the icons read clearly, the sprites look right, the damage numbers are legible and the panel lines up is judged manually. The art is placeholder.
- The room as a whole. No test builds the real room with terrain. That the props are on floor and every reference resolves is tested from the JSON, but the spawn, collision and physics behaviour is only seen in play.
- Weapons hitting the dummies. The dummy's response to damage is tested; that the sword sweep, the knife flurry and the arrows actually reach it in the physics world is judged manually.
- Interaction priority and the marker. The rule that the Sage wins over a board, the prompt and the marker are manual tests.
- Hold-E pickup.
E now fires on release so it can double as a hold to collect drops. Props sit downstream of the interact event and are checked manually.
- Wall-clock timers. The freeze zeroes the shared game time scale; the cannons use that, but timers that read the system clock keep running during a dialogue.
- Text accuracy. The keys named in the board and Sage dialogues are plain text. A rebinding would not fail a test.
- Content quality. Writing and tone are not tested.
Related pages