Skip to content

feat(cosmetics): add COMPANION cosmetic type with animation descriptor - #56

Merged
diangogav merged 1 commit into
mainfrom
feat/companion-cosmetic-type
Jun 19, 2026
Merged

feat(cosmetics): add COMPANION cosmetic type with animation descriptor#56
diangogav merged 1 commit into
mainfrom
feat/companion-cosmetic-type

Conversation

@diangogav

Copy link
Copy Markdown
Owner

What

Adds a new COMPANION cosmetic type that mirrors PLAYMAT (a multi-file glTF asset served via signed R2 URLs under a folder prefix) plus an optional structured animation descriptor.

Why

Backend prerequisite so opponents/spectators can see a player's equipped companion. The descriptor (clips/rigFile/targetHeight/orientationOffsetY) drives the client's animation strategy; getting any field wrong fails silently on the client, so the field shapes match the client contract exactly.

Changes

  • CosmeticType.COMPANION + migration: ALTER TYPE ... ADD VALUE and a nullable animation jsonb column (additive, non-destructive; isolated cosmetics_migrations history).
  • animation threaded through the domain (Cosmetic), repository, the catalog DTO, and the loadout slot. The public loadout (GetPublicLoadout) delegates to GetMyLoadout, so the opponent/spectator gate carries the descriptor without a catalog re-fetch.
  • CosmeticPostgresRepository.findById guards non-uuid ids to avoid a Postgres 500 on the client's bundled skeleton-mage default (returns a graceful NotFound).
  • Invariant: animation is only allowed on COMPANION cosmetics (Cosmetic.create).
  • Seeds the four KayKit companions (warrior/rogue/minion/mage), idempotent by assetRef.

Validation

  • 276 unit tests pass · tsc clean · biome check clean.
  • Tests assert every silent-fail gate: descriptor on the catalog DTO, on GetMyLoadout, and through the public loadout; non-uuid id -> NotFound (no 500); domain invariant; seed shape.
  • Fresh adversarial review: SHIP.

Deploy notes

  • Requires PostgreSQL 12+ (the migration's ALTER TYPE ... ADD VALUE runs inside TypeORM's transaction).
  • Run order: bun run migration:cosmetics:run before bun run seed:cosmetics.
  • R2: serve .glb as Content-Type: model/gltf-binary and allow the web origin via CORS/CORP (COEP: require-corp).

Mirror PLAYMAT's multi-file glTF model for a new COMPANION type and add an
optional structured `animation` descriptor (clips/rigFile/targetHeight/
orientationOffsetY) persisted as nullable jsonb. The descriptor is threaded to
both the catalog DTO and the loadout slot, so the public loadout — the
opponent/spectator visibility gate — carries it without a catalog re-fetch.

- Add COMPANION to CosmeticType and a migration (enum value + jsonb column)
- Guard CosmeticPostgresRepository.findById against non-uuid ids to avoid a
  Postgres 500 on the client's bundled "skeleton-mage" default
- Enforce the animation-only-on-COMPANION invariant in Cosmetic.create
- Seed the four KayKit companions (warrior/rogue/minion/mage)
@diangogav diangogav self-assigned this Jun 19, 2026
@diangogav
diangogav merged commit ced4cf7 into main Jun 19, 2026
1 check passed
@diangogav
diangogav deleted the feat/companion-cosmetic-type branch June 19, 2026 21:09
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.

1 participant