Skip to content

Render forged rival art on Forge Clash opponent cards - #822

Merged
driver727-pixel merged 1 commit into
mainfrom
copilot/fix-opponent-cards-svg-graphics
Aug 23, 2026
Merged

Render forged rival art on Forge Clash opponent cards#822
driver727-pixel merged 1 commit into
mainfrom
copilot/fix-opponent-cards-svg-graphics

Conversation

Copilot AI commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

The Forge Clash opponent card still renders the procedural SVG CardArt instead of the forged skater image that player cards show. #821 fixed which rival object reached the renderer, but that rival never carries any image layers, so CardThumbnail always falls through to its SVG path.

Two independent gaps caused this:

  1. Server looks in the wrong collection. loadRivalArtLayers queried a rivalCards collection that nothing in the app writes to. Forged boss art is saved to adminBossAssets by the Card Forge (src/pages/cardForge/useForgeSave.ts) and surfaced on the Boss Assets page.
  2. No art at all pre-match. Before a match starts the client assembles the rival from the static DISTRICT_RIVALS catalogue, which has no image URLs by design.

Changes

  • server/routes/forge.js
    • loadRivalArtLayers scans RIVAL_ART_COLLECTIONS (rivalCards, then adminBossAssets), matching on identity.name then name, before falling back to the static catalogue.
    • Extracted buildClashRival(definition, artLayers) so match start and the new endpoint emit one payload shape — stats stay server-authoritative, art layers can't overwrite them.
    • New GET /api/forge/clash/rival (authenticated, rate-limited) returning the rival plus its forged layers.
  • src/services/forge.ts — extracted the inline rival type as ForgeClashRival; added fetchForgeClashRival.
  • src/pages/ForgeClash.tsx — fetches the rival on mount for the pre-match showcase. A live match.rival still wins, except when it was snapshotted before art existed, in which case its layers are topped up from the fetched rival (same rival id only). Static catalogue remains the last-resort fallback.
  • server/test/forgeClashRoute.test.js — covers Boss Assets art resolution at match start and the new endpoint including the unauthenticated case.
for (const collectionName of RIVAL_ART_COLLECTIONS) {
  const collectionRef = adminDb.collection(collectionName);
  const byIdentity = await queryRivalArtCard(collectionRef, 'identity.name', rival);
  const artCard = byIdentity ?? await queryRivalArtCard(collectionRef, 'name', rival);
  if (artCard) return pickRivalArtLayers(artCard);
}

Note: this only renders art that already exists — a card named after the rival must be present in adminBossAssets (or rivalCards). Without one, the SVG placeholder is still the correct fallback.

… endpoint

Co-authored-by: driver727-pixel <269849721+driver727-pixel@users.noreply.github.com>
@driver727-pixel
driver727-pixel marked this pull request as ready for review August 23, 2026 14:33
Copilot AI lite review requested due to automatic review settings August 23, 2026 14:33
@driver727-pixel
driver727-pixel merged commit de92d0b into main Aug 23, 2026
3 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The rival art top-up logic can incorrectly detect/merge art layers (notably weaponImageUrl) and may overwrite existing layers with undefined, which can regress rendering in some cases.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes Forge Clash opponent cards rendering procedural SVG art pre-match by resolving forged rival art layers server-side (including Boss Assets fallback) and fetching the rival payload client-side so the pre-match lobby can render the same layered imagery as player cards.

Changes:

  • Server: resolve rival art layers by scanning multiple collections and unify the emitted rival payload shape; add a new authenticated /api/forge/clash/rival endpoint.
  • Client: introduce a shared ForgeClashRival type and add fetchForgeClashRival; fetch rival-on-mount and top up older match snapshots lacking art.
  • Tests: add coverage for Boss Assets fallback on match start and the new rival endpoint (including unauthenticated behavior).
File summaries
File Description
server/routes/forge.js Adds multi-collection rival art resolution, extracts a shared rival payload builder, and introduces a new rival fetch endpoint.
src/services/forge.ts Extracts ForgeClashRival and adds fetchForgeClashRival for the new endpoint.
src/pages/ForgeClash.tsx Fetches rival payload for pre-match display and conditionally tops up art layers for older match snapshots.
server/test/forgeClashRoute.test.js Adds tests ensuring Boss Assets art is applied and the new endpoint is authenticated and returns forged layers.
Review details

Suppressed comments (1)

src/pages/ForgeClash.tsx:607

  • When topping up liveRival with rivalPreview, the code assigns fields directly from rivalPreview (which may be undefined) and can overwrite existing liveRival layers (notably weaponImageUrl, which currently isn’t counted by hasRivalArt). Prefer nullish-coalescing (or conditional) merges so you only override with defined preview values.
      return {
        ...liveRival,
        backgroundImageUrl: rivalPreview.backgroundImageUrl,
        characterImageUrl: rivalPreview.characterImageUrl,
        frameImageUrl: rivalPreview.frameImageUrl,
        weaponImageUrl: rivalPreview.weaponImageUrl,
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/pages/ForgeClash.tsx
Comment on lines +282 to +290
function hasRivalArt(rival: ForgeClashRival | null): boolean {
if (!rival) return false;
return Boolean(
rival.characterImageUrl
|| rival.backgroundImageUrl
|| rival.frameImageUrl
|| rival.board?.imageUrl,
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants