Repository navigation
Card Presentation and Battle Inspection
Battle inspection enlarges the exact hand card without playing, discarding or upgrading it.
The hand remains underneath a translucent black backdrop with alpha 0.68; background
clicks and gameplay input are blocked until inspection closes.
| Input or condition | Result |
|---|---|
| Point at a visible hand card, then press F | Inspect the topmost eligible card under the pointer. |
| Deliberately keyboard-focus a registered card, then press F | Inspect that card; later pointer movement takes precedence over the old focus. |
| F while inspecting | Close from anywhere; repeated key-down events do not toggle repeatedly. |
| Move the pointer away | Keep the current inspection pinned. To inspect another card, close and reopen. |
| Close button, Enter or Space while inspecting | Close the panel. |
| Escape or F1 | Close inspection and return the same key event to existing pause/console handlers. |
| Type f/F into a focused text field or open console | Enter text without opening inspection. |
| Drag in progress, blocking inventory/pause/tutorial overlay, or no eligible card | Do not open inspection. |
| Source disappears, hand rebuilds, screen hides or loses focus | Close/reset as appropriate; do not retain detached hand actors. |
The panel shows a fresh card face, name, cost, type, target, upgrade state and complete authored description. Its details scroll vertically. This is not an effect glossary, an automatic hover tooltip or a base/upgraded comparison panel. The current integration is the battle hand only. It can use existing keyboard focus, but does not introduce a new hand navigation system or display an F-key instruction label.
CardWidget renders a ResolvedCard and owns no gameplay actions. Existing buttons,
Clickable and DragNDrop retain play, targeting, motion and selection behaviour.
The logical face is 225 × 456; the three frame PNGs use 450 × 912 source pixels.
These are the current frame assets, not screenshots of populated cards:
| Common | Uncommon | Rare |
|---|---|---|
![]() |
![]() |
![]() |
| Placeholder | Content and positioning |
|---|---|
| Top-left orb | Resolved cost, centred independently from the title. |
| Nameplate | Resolved name, fitted inside its own bounds. |
| Illustration | Aspect-ratio-preserving Scaling.fit artwork. |
| Middle ribbon | Card type, such as Attack or Skill. |
| Parchment | One wrapped description label with one scale across all lines. |
| Bottom plate | Target: Self, One Enemy or All Enemies. |
| Upgrade marker |
+ for upgraded snapshots. |
Rarity is communicated by the frame and remains explicit in Library metadata. The layout uses bottom-left logical coordinates:
Slot (x, y, width, height)
|
Common and Rare | Uncommon |
|---|---|---|
| Artwork | (16, 251, 193, 145) |
Same |
| Description | (23, 36, 179, 188) |
(23, 39, 179, 182) |
| Type | (75, 227, 75, 17) |
(75, 230.5, 75, 17) |
| Target | (80, 15, 65, 16) |
(80, 17, 65, 16) |
The previous artwork slot was 193 × 222: landscape illustrations were letterboxed in a
portrait opening. Reducing its height to 145 releases 77 logical units for rules without
redrawing the illustrations. The orb, nameplate and upgrade-marker positions are unchanged.
FramedCardFace scales the entire face by
min(allocatedWidth / 225, allocatedHeight / 456) and centres it. Text cannot enlarge the
frame. Current PNG windows are opaque: artworkAboveFrame = true places inset artwork
above the frame's black window but inside its safe bounds. Putting it behind the PNG makes
the illustration appear black. An unavailable frame uses CardWidget's generic fallback;
an unavailable illustration hides only the artwork actor.
CardTextFitter measures the whole wrapped label and, if needed, performs 12 binary-search
steps for a fitting scale. It restores shared font scale in finally. The last description
line therefore uses the same scale as the others. There is no minimum readable scale:
long text may still become too small in a compact hand. Inspection exposes the complete
description in a separate scrollable region, rather than claiming compact text always fits
readably.
CardKeywordHighlighter.highlight(String) matches the whole words Block, Armour, Strength,
Vulnerable, Poison and Feeble, case-insensitively, preserving their original spelling. It
emits brown #8B4513 Scene2D markup and escapes literal [ characters. Partial words do
not match, and the plain source description is not changed.
CardWidgetAssets supplies independent description-font data/caches while reusing externally
owned texture regions. Upgraded names/costs use light green #80ED99; upgraded parchment rules
use dark green #195C32. Keywords stay brown inside both base and upgraded descriptions.
Styles reset when a face is rebound to a base snapshot, without tinting neighbouring widgets.
classDiagram
class CardInspectionController {
+registerSource(Actor actor, Supplier card) void
+clearSources() void
+isOpen() boolean
+close() void
+reset() void
}
class CardInspectionPanel {
~setCard(ResolvedCard card) void
}
class CardWidget {
+setCard(ResolvedCard card) void
+getCard() ResolvedCard
}
class CardWidgetAssets
class ResolvedCard
CardInspectionController *-- CardInspectionPanel : creates lazily
CardInspectionController --> ResolvedCard : resolves current source
CardInspectionPanel *-- CardWidget : independent face actor
CardWidget --> ResolvedCard : binds snapshot
CardWidget --> CardWidgetAssets : shared visual resources
This diagram shows selected contracts. The panel is package-private; other screens use the
public controller. It is an InputComponent with priority 120, registered through the
existing entity/InputService lifecycle ahead of the battle Stage decorator at priority 10.
| API | Contract |
|---|---|
CardInspectionController(stage, skin, assets, blocked, dragging) |
All arguments are required. Reuse the screen's Stage, loaded assets and real input-blocking/drag state. Construction alone does not register input. |
registerSource(actor, supplier) |
Register the outer interactive actor. The supplier resolves the currently allowed exact copy; return null if it no longer exists. Do not capture a permanently stale snapshot. |
clearSources() |
Close and unregister all sources before replacing the hand's actors. |
close() |
Remove the open panel and restore previous keyboard/scroll focus only if still attached. |
reset() |
Close and clear held-key/pointer-preference state on screen/application focus loss. |
dispose() |
Reset, clear sources and unregister through the input component lifecycle. Does not dispose the shared skin or textures. |
Pointer targeting walks from the Stage hit actor to a registered ancestor. Keyboard targeting
does the same from keyboard focus. Eligible sources must belong to this Stage, be visible
through their ancestors and have Touchable.enabled.
The following fragment assumes an existing Stage, resource owner, UI entity and battle deck:
CardInspectionController inspection =
new CardInspectionController(stage, skin, assets, isBlocked, isDragging);
// Add before the entity is created; its normal lifecycle registers input and calls update().
uiEntity.addComponent(inspection);
inspection.registerSource(outerCardActor, () ->
battleDeck.getHand().stream()
.filter(copy -> copy.instanceId().equals(instanceId))
.findFirst()
.flatMap(copy -> cardService.getCard(copy.cardId())
.map(config -> resolver.resolve(config, copy)))
.orElse(null));isBlocked and isDragging are non-null BooleanSupplier values. Do not register a second
Stage decorator if the screen already has one. The production battle adapter supplies
inventory, console, pause and tutorial guards and resolves from the current hand by
instanceId. A definition ID cannot distinguish two owned Strike copies.
Before rebuilding sources call clearSources(), install fresh actors and register them again.
Call reset() from screen pause/hide hooks and dispose the owning entity on exit. Other
views need their own discovery/claim/filter guards before adoption; merely constructing a
controller does not implement those policies.
The panel creates a separate CardWidget; it never reparents the live hand face. It uses a
side-by-side face/details layout when available width is at least 600 logical units, otherwise
a vertical layout. Resize recomputes bounds while restoring panel focus. Unchanged snapshots
reuse the panel and retain scroll position; changed displayed values rebind it and reset rules
scroll. The controller compares displayed presentation fields, not calculated combat outcomes.
- Replace frames through
CardFrameLayoutand the rarity registry inCardWidgetAssets, not another screen-specific widget. For a 2× PNG measured from the top left, usex = pixelX / 2andy = (912 - pixelTop - pixelHeight) / 2. - Preload
CardWidgetAssets.collectTexturePaths(configs)before creating managed assets. An existing assets object does not automatically detect frames loaded afterwards. - If F does nothing, check blocking/drag state, text focus, actor visibility/touchability and the current source supplier. A valid definition alone is not enough: the owned copy must still be in the hand.
- If inspection shows an old copy, resolve from the current deck in the supplier and clear
registrations before hand rebuilds. Do not fall back to the first matching
cardId. - If typing triggers inspection or clicks reach the hand, review input priorities and guard
wiring rather than adding play listeners to
CardWidget.
The explicit F toggle avoids competing automatic hover panels. Modal input deliberately requires dismissal before another drag; Escape pass-through preserves existing pause ownership. Bounded layout prevents overflow but does not replace visual readability testing.
Verify read-only presentation, exact-copy identity, discovery permissions, input lifecycle and weighted reward generation independently, then verify their gameplay integration. Automated Scene2D tests use headless/mocked rendering; they cannot establish human readability, final composition, usability or balanced difficulty.
The verification below uses Java 21.0.8, Gradle 8.5, JUnit 5 and the existing game test
fixtures. Commands run from source/. Use an installed Java 21 JDK, and set JAVA_HOME and
Path to that installation if needed. --offline requires dependencies already cached;
omit it for a first-time build that must download dependencies.
The first five rows are useful starting points for reviewing the new interaction contracts; the remaining rows verify their data, renderer and production-flow dependencies.
| Contract | Representative tests and assertions |
|---|---|
| Explicit F inspection | CardInspectionControllerTest: 14 tests cover repeat-key suppression, pinned selection, pointer/focus precedence, duplicate/upgraded copies, no play/pile mutation, text/console input, Escape pass-through, blocking/drag state, refresh, removal, scroll, resize and disposal. |
| All-card read-only upgrades | CardUpgradePreviewTest: resolve all 26 upgrades, preserve deck/progress/configuration, deep-copy effects, reject unknown/locked/relocked/missing previews. |
| Library controls | CardLibraryDisplayTest: toggle all 26 previews back to base, retain per-card choice, verify Purify's changed cost, guard hidden/filtered/disposed controls, preserve query on resize and keep artwork paths out of details. |
| Enemy Library search | BestiaryDisplayTest: discovered-name-only results, tier intersections, stale callbacks, encountered-versus-defeated details, typing/paste/resize, scrolling and cleanup. |
| Weighted selection | WeightedCardRewardSelectionTest: exact cumulative boundaries, uniform within-bucket selection, exhaustion/renormalization, zero exclusions, empty/small pools, large finite weights, reproducibility and immutable distinct offers. |
| Frame, glyph and tint contracts | CommonCardWidgetTest, FramedCardFaceTest, CardWidgetTest: landscape art, safe opaque-window layering, all rarity slots, equal bounds, last-line glyph size, green/brown colours, rebind resets and shared-font isolation. |
| Keyword matching | CardKeywordHighlighterTest: six whole words, original casing, literal-bracket escaping and no partial-word matches. |
| Upgrade content and actual copies | CardUpgradeStandardTest, CardResolverTest, CardUpgradeSelectionTest, CardUpgradeDisplayTest: complete upgrade inventory, configured values/order, inherited fields, exact-copy commit and refreshed eligibility. |
| Literal name matching | LibraryNameSearchTest: empty/trimmed/mixed-case substrings, literal punctuation and independence from the default locale. |
| Policy schema and distribution | CardRewardPolicyTest, CardRewardPolicyLoaderTest, CardRewardSamplingTest: strict schema/numeric validation, current defaults and 10,000 seeded offers per profile. |
| Encounter handoff and fixed offers | BattleRewardProfileTest, GdxGameTest, RewardDisplayTest: capture before node clearing, normal/elite/final classification, normal–elite–normal screen isolation, defeat/tutorial separation, fixed offers, claim/retry guards and discovery on display. |
| Gameplay and persistence | AllCardsLiveCombatRegressionTest, PostBattleCardRewardPersistenceTest, AutosaveCoordinatorTest: live effects, representative upgraded Astral plays, delayed healing/order/energy outcomes, exact discard identity and acquisition → autosave/restore → next battle. |
All-card preview coverage does not mean every upgraded effect has a separate full live-combat scenario. The live suite covers every base definition and representative upgraded behaviour. Its energy assertions use actual starting energy under the current cap, not an old fixture's uncapped value. Resolution tests and gameplay tests also distinguish configured status values from the combat system's existing non-stacking status representation.
Exception tests construct inputs before assertThrows, leaving only the intended operation
inside its lambda. This prevents a fixture/list factory failure from falsely passing an
argument-validation test. Complexity refactors preserve inspection event order, stable reward
bucket order and random-call sequencing; the fixed-seed distribution is unchanged.
Full automated verification:
.\gradlew.bat --offline --no-daemon core:test spotlessJavaCheck desktop:compileJava core:javadocFocused interaction, preview and search checks:
.\gradlew.bat --offline --no-daemon core:test `
--tests 'com.csse3200.game.components.cards.*' `
--tests '*CardUpgradePreviewTest' `
--tests '*CardUpgradeStandardTest' `
--tests '*CardLibraryDisplayTest' `
--tests '*BestiaryDisplayTest' `
--tests '*LibraryNameSearchTest' spotlessJavaCheckFocused reward checks and sampling report regeneration:
.\gradlew.bat --offline --no-daemon core:test `
--tests 'com.csse3200.game.rewards.*' `
--tests '*BattleRewardProfileTest' `
--tests '*RewardDisplayTest' `
--tests '*GdxGameTest' `
--tests '*AutosaveCoordinatorTest' spotlessJavaCheckThe HTML report is source/core/build/reports/tests/test/index.html; XML results are under
source/core/build/test-results/test/. The sampling CSV is
source/core/build/reports/card-reward-probabilities.csv; Javadoc is generated under
source/core/build/docs/javadoc/.
A later filtered test run replaces the current Gradle test report; export the full result
before running a subset if both must be retained. Build reports are ignored local artifacts,
not permanent repository/wiki evidence. The sampling CSV's default revision label is generic;
attach the actual git rev-parse HEAD value and policy/catalogue when exporting it.
Verified 20 October 2026, source revision 68446fa4, branch
feature/sprint4-cards-game; only Markdown documentation was added during this verification.
| Check | Result |
|---|---|
Full core:test
|
2,395 tests across 306 suites; 0 failures, 0 errors, 0 skipped. Counts read from Gradle XML. |
spotlessJavaCheck |
Passed. |
desktop:compileJava |
Passed. |
core:javadoc |
Passed with 100 reported documentation warnings, including missing comments/tags and an external documentation URL redirect. This is not a warning-free build. |
| Seeded reward experiment | 10,000 offers per profile, seed 20261013; current results match the probability table. |
| Current in-game visual/keyboard acceptance | Not rerun for this documentation update. Use the scenarios below; automated passes do not complete them. |
No measured coverage percentage, new remote SonarCloud clearance or final-main acceptance is inferred from this local run. Repeat checks after integration changes.
Run at 1280 × 720 and 1920 × 1080. For a smaller stress allocation, use 480 × 640 to check clamping/scrolling; it is not a promise of complete small-screen usability. For every run record revision, viewport, setup, expected/actual behaviour and screenshot/video. The following scenarios are repeatable acceptance tests, not claims of newly completed runs.
Use injected in-memory discovery fixtures or disposable test progress for locked-state tests. Do not delete ordinary player progress or use a globally persisted reveal-all command merely to prepare a screenshot.
| ID | Steps | Expected result |
|---|---|---|
| UI-01 | Show short/long Common, Uncommon and Rare cards together in Library/rewards and at hand scale. | Equal face bounds; no orb/title overlap; type/target in separate slots; art fills the landscape window without covering ornaments; every description line has the same glyph size. Record whether compact text is actually readable. |
| IN-01 | Point at the top of overlapping hand cards, press F, move away, then press F again. | Exact pointed copy opens over a dim backdrop, remains pinned, then closes without playing or moving any card. |
| IN-02 | Hold F; dismiss/reopen; use Close/Enter/Space; then press Escape and F1 in separate attempts. | One toggle per physical press; close controls work; Escape/F1 reach existing pause/console behaviour. |
| IN-03 | Open a text console or start dragging; try F. Open inspection and click the backdrop. | Text remains text; drag/blocking states suppress opening; backdrop cannot activate the hand. Play/drag works normally after dismissal. |
| IN-04 | Keyboard-focus a registered card, open it, resize, remove/rebuild its source and leave/re-enter battle. | Focus targeting and scroll remain usable; removal closes it; no stale panel, listener or held-key state survives. Broader hand focus navigation must be reviewed in the owning input system. |
| UP-01 | For each discovered card, tick/untick Upgraded; include Strike and Purify. Switch cards, filter, clear, resize and reopen. | Face/details agree; Strike shows 6→12 damage, Purify 1→0 cost; green styling resets; per-card choice survives matching navigation but not a new display. Owned deck/progress are unchanged. |
| UP-02 | Create two owned copies of one definition; upgrade only one through actual upgrade selection, save/load and play both. | Only the selected ID upgrades; its resolved values survive restore; the correct copy is played/discarded. Previewing alone never commits. |
| SE-01 | Search discovered and locked exact names, mixed case, whitespace and punctuation in Card Library. | Only permitted base-name matches for nonempty queries; locked names/paths/lore never leak; Clear restores original ordering/placeholders. |
| SE-02 | Search each enemy tier; encounter and then defeat a matching enemy; clear the field. | Tier and name form an intersection; encounter exposes identity/art only; defeat reveals details; Clear retains tier; filtered-out artwork/details cannot return through an old button. |
| RW-01 | Complete normal, elite, then normal victories; separately verify final/Temple routing and defeat. | Explicit profiles do not leak between screens; Gold/Item/route handling remains intact. Do not judge configured probability from a single outcome. |
| RW-02 | Open Card, resize, rapidly choose a candidate twice, then save/load and enter another battle. | Offer does not reroll; opening alone grants nothing; one fresh base copy is granted after successful selection and survives restore. Pending-offer resume after quitting is not promised. |
The inspector's glossary/comparison controls, non-battle F consumers, compact-font floors and overflow indicator remain extension work. Do not mark their scenarios complete based on tests of the narrower delivered interaction.
Entities and Components
Input Handling
Game Screens and Areas
Encounters
Tutorials and Contextual Help (Team 2)
- Feature Overview
- Tutorial Entry and Fresh Run Transition
- Tutorial Battle Setup and Enemy Integration
- Battle Tutorial Guidance UI
- Battle Tutorial Steps and Progression
- Contextual Event Help and Tutorial Content
- Tutorial Acceptance Test Plan
- Card Keyword Tooltips
- Map Introduction
- Tutorial Status Teaching
- Tutorial Victory Screen


