아바타 v3 세션 연결 — P1 서연 리노컷 아바타와 음성 립싱크
Some checks are pending
API contract / OpenAPI type drift (push) Waiting to run
Some checks are pending
API contract / OpenAPI type drift (push) Waiting to run
- ClientAvatar가 리그 레지스트리(v3/rigs)로 P1을 판정해 그림 영역을 ClientAvatarV3로 그린다(로드 중·실패 시 기존 SVG, VITE_AVATAR_V3=0이면 끔) - 발화 구동 공용 모듈 speechDriver: Lab과 세션이 함께 쓰고, TTS AudioBuffer를 오디오 시계로 표본해 립싱크·발화 동반층·지문 cue를 돌린다 - Session: TTS 재생 시작 시 발화(원문·버퍼·시작 시각) 전달, 음성 실패 시 텍스트 타이밍 발화, 정지 경로에서 해제, 개방도·겉표정 강도 전달 - 정지 화면(시작 전·썸네일)도 한 프레임을 그리고, 박스 실측 폭으로 상반신/얼굴 크롭을 고른다 - 세션 발화 E2E(오디오·실패 경로), P1 레이아웃 게이트, P1 표정 시험을 v3 기대로 갱신
This commit is contained in:
parent
ecb36d123f
commit
613bcb603e
14 changed files with 1000 additions and 182 deletions
|
|
@ -666,6 +666,62 @@ test.describe("learner session full-screen layout", () => {
|
|||
}
|
||||
});
|
||||
|
||||
// P1 서연은 v3(리노컷) 아바타로 그린다(결정문 §8.5). 위 "dense viewport" 스윕을 P1로도
|
||||
// 돌려 v3 래퍼(절대 위치·원형 클립)가 기존 SVG와 같은 레이아웃 게이트를 통과하는지 본다.
|
||||
test("keeps critical session controls visible across dense viewport sizes for the v3 avatar (P1)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await signInAsLearner(page);
|
||||
|
||||
const viewports = [
|
||||
{ width: 1366, height: 768 },
|
||||
{ width: 1180, height: 768 },
|
||||
{ width: 1024, height: 768 },
|
||||
{ width: 881, height: 768 },
|
||||
{ width: 820, height: 1180 },
|
||||
{ width: 390, height: 844 },
|
||||
{ width: 320, height: 568 },
|
||||
];
|
||||
|
||||
await page.setViewportSize(viewports[0]);
|
||||
await page.goto("/learn/session/P1");
|
||||
await page.getByRole("button", { name: "회기 시작" }).click();
|
||||
await completeAlliancePreCheckpoint(page);
|
||||
await expect(page.locator(".sx-page.sx-page--active")).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
for (const viewport of viewports) {
|
||||
await page.setViewportSize(viewport);
|
||||
await page.evaluate(() => new Promise(requestAnimationFrame));
|
||||
|
||||
await expect(page.locator(".sx-page.sx-page--active")).toBeVisible({ timeout: 15_000 });
|
||||
await expectNoDocumentOverflow(page);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await expectSessionControlsInsideViewport(page);
|
||||
await expectNoVisibleSessionPanelOverlap(page);
|
||||
await expectMainControlsUnclipped(page);
|
||||
await expectSessionPageHeightToMatchViewport(page);
|
||||
await expectActiveSessionUsableLayout(page);
|
||||
|
||||
/* 320×568 저높이 폰은 session.css가 아바타 오브(.sx-orb-wrap)를 숨긴다(기존 SVG도 같다).
|
||||
보일 때는 v3 래퍼가 스테이지 원 자리를 정확히 차지해야 한다. */
|
||||
const orbShown = !(viewport.width === 320 && viewport.height === 568);
|
||||
const stage = page.locator(".sx-page--active .vg-avatar__stage").first();
|
||||
if (orbShown) {
|
||||
await expect(stage).toBeVisible();
|
||||
const v3 = stage.locator('[data-avatar-renderer="linocut"]');
|
||||
await expect(v3).toHaveAttribute("data-load-state", "ready", { timeout: 15_000 });
|
||||
const [stageBox, v3Box] = await Promise.all([stage.boundingBox(), v3.boundingBox()]);
|
||||
expect(stageBox, "아바타 스테이지 좌표").not.toBeNull();
|
||||
expect(v3Box, "v3 래퍼 좌표").not.toBeNull();
|
||||
for (const key of ["x", "y", "width", "height"] as const) {
|
||||
expect(Math.abs(stageBox![key] - v3Box![key]), `v3 래퍼 ${key}`).toBeLessThanOrEqual(1);
|
||||
}
|
||||
} else {
|
||||
await expect(page.locator(".sx-page--active .sx-orb-wrap")).toBeHidden();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("does not leave an unsaved local transcript when a text turn is rejected", async ({ page }) => {
|
||||
await signInAsLearner(page);
|
||||
const persona = await fetchAvailablePersona(page, 1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue