From fc3c7ae0c0c67b3d70d96f39c03698e4285df62d Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Thu, 1 Oct 2026 18:45:06 +0700 Subject: [PATCH] feat(gacha): render the beta wish in portrait /api/gachabeta now returns a portrait video with the requested width as its long edge: 640 renders 360x640 and 854 renders 480x854. The request body is unchanged, and /api/gacha stays landscape. The pack sits higher so it stays in frame while the card rises. --- renderer/README.md | 5 +++-- renderer/src/gacha-beta/page/page.css | 2 +- renderer/src/gacha-beta/wish-plan.js | 10 ++++++++++ renderer/src/render/render-gacha-beta.js | 13 ++++++++----- renderer/test/gacha-beta-wish-plan.test.js | 13 ++++++++++++- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/renderer/README.md b/renderer/README.md index aa844ad..99aba83 100644 --- a/renderer/README.md +++ b/renderer/README.md @@ -67,12 +67,13 @@ only draws it. ### Gacha wish beta `POST /api/gachabeta` takes the same body and returns the same response as -`/api/gacha`, rendering a 6-second beta style instead: a collectible card pack +`/api/gacha`, rendering a 6-second portrait beta style instead: a collectible card pack from [pack-cards](https://github.com/paubineau/pack-cards) is torn open, and its card shows the request's `label`, its rank, and its stars. As on `/api/gacha`, `rarity` picks the rank (`B` for 3★, `A` for 4★, `S` for 5★), the star count, the card's material (rare, epic, legendary), and the pack's -colour (blue, purple, gold). +colour (blue, purple, gold). The video is portrait, with `width` as its long +edge: `640` renders 360×640 and `854` renders 480×854. pack-cards animates on the browser clock, so this route does not use Remotion compositions. `src/render/render-gacha-beta.js` keeps one headless Chrome per diff --git a/renderer/src/gacha-beta/page/page.css b/renderer/src/gacha-beta/page/page.css index dce7611..45635ca 100644 --- a/renderer/src/gacha-beta/page/page.css +++ b/renderer/src/gacha-beta/page/page.css @@ -30,7 +30,7 @@ body { /* Sit the pack below centre so the card rising out of it stays in frame. */ #stage .recap-pack-stack { - margin: 24vh 0 0; + margin: 14vh 0 0; width: min(36vh, 60vw); } diff --git a/renderer/src/gacha-beta/wish-plan.js b/renderer/src/gacha-beta/wish-plan.js index f8ebe15..8e56f7c 100644 --- a/renderer/src/gacha-beta/wish-plan.js +++ b/renderer/src/gacha-beta/wish-plan.js @@ -6,6 +6,7 @@ import path from 'node:path'; import {getRankLetter, getRarityPalette} from '../remotion/gacha-timeline.js'; +import {gachaFrameSizes} from '../schemas/gacha-request.js'; /** @typedef {import('../schemas/gacha-request.js').GachaRenderRequest} GachaRenderRequest */ /** @typedef {import('../remotion/gacha-timeline.js').GachaRarity} GachaRarity */ @@ -25,6 +26,15 @@ export const wishTimeline = Object.freeze({ dragEnd: 1, }); +/** + * The beta wish is portrait: the request's `width` (640 or 854) is the long + * edge, so the frame is the /api/gacha frame turned upright. + * + * @param {GachaRenderRequest['width']} width + * @returns {{width: number, height: number}} + */ +export const getWishFrameSize = (width) => ({width: gachaFrameSizes[width], height: width}); + /** pack-cards rarity profile and glow preset for each requested rarity. */ const rarityLooks = Object.freeze({ 3: {rarity: 'rare', glow: /** @type {const} */ ('silver')}, diff --git a/renderer/src/render/render-gacha-beta.js b/renderer/src/render/render-gacha-beta.js index 0e796cf..be94193 100644 --- a/renderer/src/render/render-gacha-beta.js +++ b/renderer/src/render/render-gacha-beta.js @@ -7,13 +7,13 @@ import {ensureBrowser, makeCancelSignal, RenderInternals} from '@remotion/render import { gachaBetaTotalSeconds, getDragPointer, + getWishFrameSize, getWishPageProps, resolveWishAsset, wishOrigin, } from '../gacha-beta/wish-plan.js'; import {RenderTimeoutError} from '../lib/render-errors.js'; import {cleanupTempDir, createRenderTempDir} from '../lib/tmp-files.js'; -import {gachaFrameSizes} from '../schemas/gacha-request.js'; import {createCdpConnection} from './cdp-pipe.js'; /** @@ -158,7 +158,10 @@ export const closeGachaBetaBrowser = async () => { * @returns {Promise} */ export const warmGachaBetaBrowser = async () => { - await renderGachaBetaVideo({label: 'Warm-up', rarity: 5, fps: 24, width: 640, seed: 1}, {timeoutInMilliseconds: 120000}); + await renderGachaBetaVideo( + {label: 'Warm-up', rarity: 5, fps: 24, width: 640, seed: 1}, + {timeoutInMilliseconds: 120000}, + ); }; /** @@ -194,13 +197,13 @@ const captureFrames = async (cdp, sessionId, request, dir, guard) => { */ const send = (method, params) => guard(cdp.send(method, params, sessionId)); /** @param {string} expression */ - const evaluate = async (expression) => (await send('Runtime.evaluate', {expression, returnByValue: true})).result?.value; + const evaluate = async (expression) => + (await send('Runtime.evaluate', {expression, returnByValue: true})).result?.value; await send('Page.enable'); await send('Fetch.enable', {patterns: [{urlPattern: '*'}]}); await send('Emulation.setDeviceMetricsOverride', { - width: request.width, - height: gachaFrameSizes[request.width], + ...getWishFrameSize(request.width), deviceScaleFactor: 1, mobile: false, }); diff --git a/renderer/test/gacha-beta-wish-plan.test.js b/renderer/test/gacha-beta-wish-plan.test.js index 6f49ffa..82ff3f5 100644 --- a/renderer/test/gacha-beta-wish-plan.test.js +++ b/renderer/test/gacha-beta-wish-plan.test.js @@ -3,6 +3,7 @@ import {describe, expect, test} from 'vitest'; import { gachaBetaTotalSeconds, getDragPointer, + getWishFrameSize, getWishPageProps, resolveWishAsset, wishOrigin, @@ -14,12 +15,22 @@ const roots = {page: path.resolve('/srv/page'), packCards: path.resolve('/srv/pa describe('gacha beta wish plan', () => { test('the page shows the request label with the same rank and stars as /api/gacha', () => { const base = {label: 'Bún bò', fps: /** @type {const} */ (24), width: /** @type {const} */ (640), seed: 1}; - expect(getWishPageProps({...base, rarity: 3})).toMatchObject({label: 'Bún bò', rank: 'B', stars: 3, rarity: 'rare'}); + expect(getWishPageProps({...base, rarity: 3})).toMatchObject({ + label: 'Bún bò', + rank: 'B', + stars: 3, + rarity: 'rare', + }); expect(getWishPageProps({...base, rarity: 4})).toMatchObject({rank: 'A', stars: 4, rarity: 'epic'}); expect(getWishPageProps({...base, rarity: 5})).toMatchObject({rank: 'S', stars: 5, rarity: 'legendary'}); expect(getWishPageProps({...base, rarity: 5}).artwork).toEqual({accent: '#ffd36b', tint: '#7a4a10'}); }); + test('the frame is portrait, with the requested width as its long edge', () => { + expect(getWishFrameSize(640)).toEqual({width: 360, height: 640}); + expect(getWishFrameSize(854)).toEqual({width: 480, height: 854}); + }); + test('the drag sweeps left to right across the seal, then lets go', () => { const {dragStart, dragEnd} = wishTimeline; expect(getDragPointer(dragStart - 0.01)).toBeNull();