Skip to content

Card Presentation and Battle Inspection

ataipham edited this page Oct 19, 2026 · 2 revisions

Player interaction

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.

Shared frame layout

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
image image image
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.

Keyword and upgrade colours

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.

Inspection architecture and API

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
Loading

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.

Adopting inspection in another screen

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.

Extension and troubleshooting

  • Replace frames through CardFrameLayout and the rarity registry in CardWidgetAssets, not another screen-specific widget. For a 2× PNG measured from the top left, use x = pixelX / 2 and y = (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.

Test Plan:

Scope and environment

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.

Automated coverage

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.

Running the checks

Full automated verification:

.\gradlew.bat --offline --no-daemon core:test spotlessJavaCheck desktop:compileJava core:javadoc

Focused 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' spotlessJavaCheck

Focused 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' spotlessJavaCheck

The 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.

Recorded verification

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.

Manual gameplay and visual acceptance

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.

Table of Contents

Home

Game Engine

Getting Started

Entities and Components

Service Locator

Loading Resources

Logging

Unit Testing

Debug Terminal

Input Handling

UI

Animations

Audio

AI

Physics

Game Screens and Areas

Terrain

Concurrency & Threading

Settings

Encounters
Tutorials and Contextual Help (Team 2)
Cards (Team 6)
Story (Team 6)
Level design, battle system, turn order, spawn (Team 3)
Cards, Save/Load & Bestiary (Team 5)
Rewards and Battle Settlement (Team 7)

Troubleshooting

MacOS Setup Guide

Clone this wiki locally