Files

2348 lines
78 KiB
Plaintext
Raw Permalink Normal View History

2026-10-14 10:01:46 +00:00
$ npx vitest run tests/App.test.tsx tests/neural-release.test.ts
RUN v5.0.4 /home/hermes/worktrees/find-word-v0.2.3
❯ tests/neural-release.test.ts (4 tests | 1 failed) 54ms
× README는 현재 리뷰 증거 경로와 정확한 라운드 보너스를 안내한다 44ms
❯ tests/App.test.tsx (23 tests | 6 failed) 6299ms
× 오디오 started=false 중 종료는 즉시 정지하고 결과를 거쳐 홈으로 간다 227ms
× 오디오 started=true 중 종료는 즉시 정지하고 결과를 거쳐 홈으로 간다 208ms
× 5단계 완료는 6단계 대신 같은 단계의 새 세 문항을 시작한다 601ms
× 세 문항 라운드에서 가능한 연속 정답 보너스 상한을 설명한다 100ms
× 현재 문항 진행률은 1/3→2/3→3/3이고 1단계 만점은 330점이다 221ms
× 무응답 종료는 정답률 대신 미응답 상태와 단계별 미진행을 보여 준다 215ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 7 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/App.test.tsx > 오디오 started=false 중 종료는 즉시 정지하고 결과를 거쳐 홈으로 간다
FAIL tests/App.test.tsx > 오디오 started=true 중 종료는 즉시 정지하고 결과를 거쳐 홈으로 간다
TestingLibraryElementError: Unable to find an accessible element with the role "heading" and name "문제 풀이를 중간에 종료했어요."
Here are the accessible roles:
link:
Name "본문으로 건너뛰기":
<a
class="skip-link"
href="#main"
/>
Name "환경음 출처·라이선스":
<a
href="/AMBIENCE_NOTICES.md"
/>
--------------------------------------------------
banner:
Name "":
<header
class="header wrap"
/>
--------------------------------------------------
heading:
Name "단어를 맞춰라":
<h1 />
Name "문제 풀이를 종료했어요.":
<h2
tabindex="-1"
/>
--------------------------------------------------
main:
Name "":
<main
class="wrap"
id="main"
tabindex="-1"
/>
--------------------------------------------------
paragraph:
Name "":
<p
class="muted"
/>
Name "":
<p />
Name "":
<p
class="muted"
/>
Name "":
<p />
Name "":
<p />
--------------------------------------------------
strong:
Name "":
<strong />
Name "":
<strong />
Name "":
<strong />
--------------------------------------------------
table:
Name "난이도별 결과":
<table
aria-label="난이도별 결과"
/>
--------------------------------------------------
rowgroup:
Name "":
<thead />
Name "":
<tbody />
--------------------------------------------------
row:
Name "난이도 SNR 맞힌 문제":
<tr />
Name "1단계 SNR +6 dB 0 / 0":
<tr />
Name "2단계 SNR +3 dB 0 / 0":
<tr />
Name "3단계 SNR 0 dB 0 / 0":
<tr />
Name "4단계 SNR -3 dB 0 / 0":
<tr />
Name "5단계 SNR -6 dB 0 / 0":
<tr />
--------------------------------------------------
columnheader:
Name "난이도":
<th
scope="col"
/>
Name "SNR":
<th
scope="col"
/>
Name "맞힌 문제":
<th
scope="col"
/>
--------------------------------------------------
rowheader:
Name "1단계":
<th
scope="row"
/>
Name "2단계":
<th
scope="row"
/>
Name "3단계":
<th
scope="row"
/>
Name "4단계":
<th
scope="row"
/>
Name "5단계":
<th
scope="row"
/>
--------------------------------------------------
cell:
Name "SNR +6 dB":
<td />
Name "0 / 0":
<td />
Name "SNR +3 dB":
<td />
Name "0 / 0":
<td />
Name "SNR 0 dB":
<td />
Name "0 / 0":
<td />
Name "SNR -3 dB":
<td />
Name "0 / 0":
<td />
Name "SNR -6 dB":
<td />
Name "0 / 0":
<td />
--------------------------------------------------
button:
Name "처음 화면으로":
<button
class="secondary"
/>
--------------------------------------------------
slider:
Name "전체 음량":
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
--------------------------------------------------
status:
Name "":
<output
for="volume"
/>
--------------------------------------------------
contentinfo:
Name "":
<footer
class="footer wrap"
/>
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<a
class="skip-link"
href="#main"
>
본문으로 건너뛰기
</a>
<header
class="header wrap"
>
<div
class="brand"
>
<span
aria-hidden="true"
class="brand-mark"
>
<i />
<i />
<i />
<i />
</span>
<h1>
단어를 맞춰라
</h1>
<span
class="version"
>
v0.2.3
</span>
</div>
<div
class="header-actions"
>
<span
class="little-dot"
/>
소리에 집중하는 시간
</div>
</header>
<main
class="wrap"
id="main"
tabindex="-1"
>
<section
class="results-panel panel"
>
<span
class="eyebrow"
>
지금까지의 소리 탐험
</span>
<div
class="result-mascot"
>
<div
aria-hidden="true"
class="mascot small"
>
<span
class="ear left"
/>
<span
class="ear right"
/>
<span
class="eye left"
/>
<span
class="eye right"
/>
<span
class="smile"
/>
<b>
S
</b>
</div>
</div>
<h2
tabindex="-1"
>
문제 풀이를 종료했어요.
</h2>
<p
class="muted"
>
작은 소리에 집중한 당신, 다음 탐험에서도 만나요.
</p>
<p>
응답 
0
문항 · 정답 
0
문항
</p>
<div
class="stats"
>
<div>
<strong>
0
</strong>
<span>
총 점수
</span>
</div>
<div>
<strong>
0
%
</strong>
<span>
정답률 · 
0
/
0
</span>
</div>
<div>
<strong>
0
<small>
회
</small>
</strong>
<span>
최고 연속 정답
</span>
</div>
</div>
<table
aria-label="난이도별 결과"
>
<thead>
<tr>
<th
scope="col"
>
난이도
</th>
<th
scope="col"
>
SNR
</th>
<th
scope="col"
>
맞힌 문제
</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
1
단계
</th>
<td>
SNR +6 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
2
단계
</th>
<td>
SNR +3 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
3
단계
</th>
<td>
SNR 0 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
4
단계
</th>
<td>
SNR -3 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
5
단계
</th>
<td>
SNR -6 dB
</td>
<td>
0
 / 
0
</td>
</tr>
</tbody>
</table>
<p
class="muted"
>
단계 × 100점 + 연속 정답 보너스 (라운드 내 최대
 
20
점)
</p>
<button
class="secondary"
>
처음 화면으로
</button>
</section>
<div
class="volume-row"
>
<label
for="volume"
>
<span
aria-hidden="true"
>
◖))
</span>
 전체 음량
</label>
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
<output
for="volume"
>
35
%
</output>
</div>
</main>
<footer
class="footer wrap"
>
<p>
Designed by OZ at Heat-T Medical Inc.
</p>
<p>
청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다.
</p>
<span>
개인정보 수집 없이, 오직 플레이만.
</span>
<a
href="/AMBIENCE_NOTICES.md"
>
환경음 출처·라이선스
</a>
</footer>
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:415:14
413| expect(f.audio.stop).toHaveBeenCalledOnce();
414| expect(
415| screen.getByRole("heading", { name: "문제 풀이를 중간에 종료했어요." }),
| ^
416| ).toHaveFocus();
417| expect(screen.getByText("응답 0문항 · 정답 0문항")).toBeInTheDocument();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/8]⎯
FAIL tests/App.test.tsx > 5단계 완료는 6단계 대신 같은 단계의 새 세 문항을 시작한다
Error: expect(element).toHaveAttribute("value", "1") // element.getAttribute("value") === "1"
Expected the element to have attribute:
value="1"
Received:
value="0"
❯ tests/App.test.tsx:477:43
475| );
476| expect(screen.getByRole("progressbar")).toHaveAttribute("max", "3");
477| expect(screen.getByRole("progressbar")).toHaveAttribute("value", "1"…
| ^
478| await f.finish();
479| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/8]⎯
FAIL tests/App.test.tsx > 세 문항 라운드에서 가능한 연속 정답 보너스 상한을 설명한다
TestingLibraryElementError: Unable to find an element with the text: 정답마다 단계 × 100점 + 연속 정답 보너스 (문항별 최대 20점, 라운드 합계 최대 30점). This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
Ignored nodes: comments, script, style
<body>
<div>
<a
class="skip-link"
href="#main"
>
본문으로 건너뛰기
</a>
<header
class="header wrap"
>
<div
class="brand"
>
<span
aria-hidden="true"
class="brand-mark"
>
<i />
<i />
<i />
<i />
</span>
<h1>
단어를 맞춰라
</h1>
<span
class="version"
>
v0.2.3
</span>
</div>
<div
class="header-actions"
>
<span
class="little-dot"
/>
소리에 집중하는 시간
</div>
</header>
<main
class="wrap"
id="main"
tabindex="-1"
>
<section
class="results-panel panel"
>
<span
class="eyebrow"
>
지금까지의 소리 탐험
</span>
<div
class="result-mascot"
>
<div
aria-hidden="true"
class="mascot small"
>
<span
class="ear left"
/>
<span
class="ear right"
/>
<span
class="eye left"
/>
<span
class="eye right"
/>
<span
class="smile"
/>
<b>
S
</b>
</div>
</div>
<h2
tabindex="-1"
>
문제 풀이를 종료했어요.
</h2>
<p
class="muted"
>
작은 소리에 집중한 당신, 다음 탐험에서도 만나요.
</p>
<p>
응답 
0
문항 · 정답 
0
문항
</p>
<div
class="stats"
>
<div>
<strong>
0
</strong>
<span>
총 점수
</span>
</div>
<div>
<strong>
0
%
</strong>
<span>
정답률 · 
0
/
0
</span>
</div>
<div>
<strong>
0
<small>
회
</small>
</strong>
<span>
최고 연속 정답
</span>
</div>
</div>
<table
aria-label="난이도별 결과"
>
<thead>
<tr>
<th
scope="col"
>
난이도
</th>
<th
scope="col"
>
SNR
</th>
<th
scope="col"
>
맞힌 문제
</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
1
단계
</th>
<td>
SNR +6 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
2
단계
</th>
<td>
SNR +3 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
3
단계
</th>
<td>
SNR 0 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
4
단계
</th>
<td>
SNR -3 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
5
단계
</th>
<td>
SNR -6 dB
</td>
<td>
0
 / 
0
</td>
</tr>
</tbody>
</table>
<p
class="muted"
>
단계 × 100점 + 연속 정답 보너스 (라운드 내 최대
 
20
점)
</p>
<button
class="secondary"
>
처음 화면으로
</button>
</section>
<div
class="volume-row"
>
<label
for="volume"
>
<span
aria-hidden="true"
>
◖))
</span>
 전체 음량
</label>
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
<output
for="volume"
>
35
%
</output>
</div>
</main>
<footer
class="footer wrap"
>
<p>
Designed by OZ at Heat-T Medical Inc.
</p>
<p>
청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다.
</p>
<span>
개인정보 수집 없이, 오직 플레이만.
</span>
<a
href="/AMBIENCE_NOTICES.md"
>
환경음 출처·라이선스
</a>
</footer>
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:507:12
505| await f.user.click(screen.getByRole("button", { name: "문제 풀이 종료" }));
506| expect(
507| screen.getByText("정답마다 단계 × 100점 + 연속 정답 보너스 (문항별 최대 20점, 라운드 합계 최…
| ^
508| ).toBeInTheDocument();
509| await f.finish();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/8]⎯
FAIL tests/App.test.tsx > 현재 문항 진행률은 1/3→2/3→3/3이고 1단계 만점은 330점이다
TestingLibraryElementError: Unable to find an accessible element with the role "progressbar" and name "현재 문항 진행률"
Here are the accessible roles:
link:
Name "본문으로 건너뛰기":
<a
class="skip-link"
href="#main"
/>
Name "환경음 출처·라이선스":
<a
href="/AMBIENCE_NOTICES.md"
/>
--------------------------------------------------
banner:
Name "":
<header
class="header wrap"
/>
--------------------------------------------------
heading:
Name "단어를 맞춰라":
<h1 />
Name "카페":
<h2 />
Name "소리 사이에 집중해 보세요.":
<h2
tabindex="-1"
/>
--------------------------------------------------
button:
Name "문제 풀이 종료":
<button
class="text-button"
/>
Name "듣는 중…":
<button
class="secondary"
disabled=""
/>
Name "듣기 중단":
<button
class="text-button"
/>
--------------------------------------------------
main:
Name "":
<main
class="wrap"
id="main"
tabindex="-1"
/>
--------------------------------------------------
region:
Name "단어 찾기 게임":
<section
aria-label="단어 찾기 게임"
class="game-layout"
/>
--------------------------------------------------
progressbar:
Name "전체 진행률":
<progress
aria-label="전체 진행률"
max="3"
value="0"
/>
--------------------------------------------------
complementary:
Name "":
<aside
class="environment-panel"
/>
--------------------------------------------------
paragraph:
Name "":
<p
class="eyebrow"
/>
Name "":
<p />
Name "":
<p
class="db-note"
/>
Name "":
<p
id="snr-help"
/>
Name "":
<p />
Name "":
<p />
Name "":
<p />
--------------------------------------------------
slider:
Name "목표 음성 SNR":
<input
aria-describedby="snr-help"
id="snr"
max="12"
min="-12"
step="1"
type="range"
value="6"
/>
Name "전체 음량":
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
--------------------------------------------------
status:
Name "":
<output
for="snr"
/>
Name "":
<output
for="volume"
/>
Name "":
<p
role="status"
/>
--------------------------------------------------
contentinfo:
Name "":
<footer
class="footer wrap"
/>
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<a
class="skip-link"
href="#main"
>
본문으로 건너뛰기
</a>
<header
class="header wrap"
>
<div
class="brand"
>
<span
aria-hidden="true"
class="brand-mark"
>
<i />
<i />
<i />
<i />
</span>
<h1>
단어를 맞춰라
</h1>
<span
class="version"
>
v0.2.3
</span>
</div>
<div
class="header-actions"
>
<button
class="text-button"
>
문제 풀이 종료
</button>
<span
class="little-dot"
/>
소리에 집중하는 시간
</div>
</header>
<main
class="wrap"
id="main"
tabindex="-1"
>
<section
aria-label="단어 찾기 게임"
class="game-layout"
>
<div
class="game-top"
>
<span
class="eyebrow"
>
나의 소리 탐험
</span>
<span>
문제 
<b>
01
</b>
 /
 
3
</span>
</div>
<progress
aria-label="전체 진행률"
max="3"
value="0"
/>
<div
class="game-columns"
>
<aside
class="environment-panel"
>
<p
class="eyebrow"
>
지금 듣고 있는 공간
</p>
<h2>
카페
</h2>
<p>
부드러운 웅성임과 잔의 울림
</p>
<div
aria-hidden="true"
class="mascot small"
>
<span
class="ear left"
/>
<span
class="ear right"
/>
<span
class="eye left"
/>
<span
class="eye right"
/>
<span
class="smile"
/>
<b>
S
</b>
</div>
<div
aria-hidden="true"
class="sound-wave active"
>
<i
style="height: 10px; animation-delay: 0s;"
/>
<i
style="height: 33.799955450859244px; animation-delay: 0.05s;"
/>
<i
style="height: 16.132986448643948px; animation-delay: 0.1s;"
/>
<i
style="height: 32.219552375865575px; animation-delay: 0.15000000000000002s;"
/>
<i
style="height: 21.858720427326595px; animation-delay: 0.2s;"
/>
<i
style="height: 29.163690702963766px; animation-delay: 0.25s;"
/>
<i
style="height: 26.796992502245015px; animation-delay: 0.30000000000000004s;"
/>
<i
style="height: 24.835290693688798px; animation-delay: 0.35000000000000003s;"
/>
<i
style="height: 30.619883556555898px; animation-delay: 0.4s;"
/>
<i
style="height: 35.39139668035928px; animation-delay: 0.45s;"
/>
<i
style="height: 71.52943948029164px; animation-delay: 0.5s;"
/>
<i
style="height: 19.53593765210872px; animation-delay: 0.55s;"
/>
<i
style="height: 73.98674560913082px; animation-delay: 0.6000000000000001s;"
/>
<i
style="height: 16.952742107140878px; animation-delay: 0.65s;"
/>
<i
style="height: 72.19510052760725px; animation-delay: 0.7000000000000001s;"
/>
<i
style="height: 32.979734657418774px; animation-delay: 0.75s;"
/>
<i
style="height: 66.27347594564635px; animation-delay: 0.8s;"
/>
<i
style="height: 47.480789758355044px; animation-delay: 0.8500000000000001s;"
/>
<i
style="height: 56.615089141222136px; animation-delay: 0.9s;"
/>
<i
style="height: 59.492984932238116px; animation-delay: 0.9500000000000001s;"
/>
<i
style="height: 43.861291911681526px; animation-delay: 1s;"
/>
<i
style="height: 68.21866699734562px; animation-delay: 1.05s;"
/>
<i
style="height: 28.858982496016484px; animation-delay: 1.1s;"
/>
<i
style="height: 73.07841912260051px; animation-delay: 1.1500000000000001s;"
/>
<i
style="height: 12.60436847039135px; animation-delay: 1.2000000000000002s;"
/>
<i
style="height: 73.74953619965402px; animation-delay: 1.25s;"
/>
<i
style="height: 23.8231850360459px; animation-delay: 1.3s;"
/>
<i
style="height: 32.57029511072814px; animation-delay: 1.35s;"
/>
<i
style="height: 20.99981090780238px; animation-delay: 1.4000000000000001s;"
/>
<i
style="height: 29.735764963203792px; animation-delay: 1.4500000000000002s;"
/>
<i
style="height: 26.085500220240995px; animation-delay: 1.5s;"
/>
<i
style="height: 25.59070868046883px; animation-delay: 1.55s;"
/>
<i
style="height: 30.10305417152866px; animation-delay: 1.6s;"
/>
<i
style="height: 20.410372983416416px; animation-delay: 1.6500000000000001s;"
/>
<i
style="height: 32.785692656480826px; animation-delay: 1.7000000000000002s;"
/>
</div>
<div
class="level-tag"
>
1
단계 · 
SNR +6 dB
</div>
<p
class="db-note"
>
소리에 익숙해지기
</p>
<div
class="snr-control"
>
<label
for="snr"
>
목표 음성 SNR
</label>
<input
aria-describedby="snr-help"
id="snr"
max="12"
min="-12"
step="1"
type="range"
value="6"
/>
<output
for="snr"
>
SNR +6 dB
</output>
<p
id="snr-help"
>
음성이 배경보다 6 dB 커요.
 범위 −12~+12 dB · 1 dB 간격. 양수는 음성이 더 크게, 0은 같게, 음수는 더 작게 들려요. 변경은 다음 재생·재청취부터 적용돼요.
</p>
</div>
<div
class="volume-row"
>
<label
for="volume"
>
<span
aria-hidden="true"
>
◖))
</span>
 전체 음량
</label>
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
<output
for="volume"
>
35
%
</output>
</div>
<div
class="mini-stats"
>
<span>
점수 
<b>
0
</b>
</span>
<span>
연속 정답 
<b>
0
</b>
</span>
</div>
</aside>
<div
class="question-panel panel"
>
<div
class="question-meta"
>
<span>
두 번의 힌트
</span>
<span>
객관식
</span>
</div>
<div
class="listening"
>
<span
aria-hidden="true"
class="listening-icon"
>
♬
</span>
<h2
tabindex="-1"
>
소리 사이에 집중해 보세요.
</h2>
<p>
문장이 끝나면 질문이 나타나요.
<br />
작은 단어 하나를 기억해 주세요.
</p>
<p
role="status"
>
에스의 목소리를 듣고 있어요…
</p>
</div>
<div
class="play-controls"
>
<button
class="secondary"
disabled=""
>
듣는 중…
</button>
<button
class="text-button"
>
듣기 중단
</button>
<small>
다시 들어도 점수는 줄어들지 않아요.
</small>
</div>
</div>
</div>
</section>
</main>
<footer
class="footer wrap"
>
<p>
Designed by OZ at Heat-T Medical Inc.
</p>
<p>
청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다.
</p>
<span>
개인정보 수집 없이, 오직 플레이만.
</span>
<a
href="/AMBIENCE_NOTICES.md"
>
환경음 출처·라이선스
</a>
</footer>
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:518:24
516| await f.user.click(screen.getByRole("button", { name: "게임 시작 · 소리 듣기…
517| for (let index = 0; index < 3; index++) {
518| const bar = screen.getByRole("progressbar", { name: "현재 문항 진행률" });
| ^
519| expect(bar).toHaveAttribute("max", "3");
520| expect(bar).toHaveAttribute("value", String(index + 1));
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/8]⎯
FAIL tests/App.test.tsx > 무응답 종료는 정답률 대신 미응답 상태와 단계별 미진행을 보여 준다
TestingLibraryElementError: Unable to find an accessible element with the role "heading" and name "문제 풀이를 중간에 종료했어요."
Here are the accessible roles:
link:
Name "본문으로 건너뛰기":
<a
class="skip-link"
href="#main"
/>
Name "환경음 출처·라이선스":
<a
href="/AMBIENCE_NOTICES.md"
/>
--------------------------------------------------
banner:
Name "":
<header
class="header wrap"
/>
--------------------------------------------------
heading:
Name "단어를 맞춰라":
<h1 />
Name "문제 풀이를 종료했어요.":
<h2
tabindex="-1"
/>
--------------------------------------------------
main:
Name "":
<main
class="wrap"
id="main"
tabindex="-1"
/>
--------------------------------------------------
paragraph:
Name "":
<p
class="muted"
/>
Name "":
<p />
Name "":
<p
class="muted"
/>
Name "":
<p />
Name "":
<p />
--------------------------------------------------
strong:
Name "":
<strong />
Name "":
<strong />
Name "":
<strong />
--------------------------------------------------
table:
Name "난이도별 결과":
<table
aria-label="난이도별 결과"
/>
--------------------------------------------------
rowgroup:
Name "":
<thead />
Name "":
<tbody />
--------------------------------------------------
row:
Name "난이도 SNR 맞힌 문제":
<tr />
Name "1단계 SNR +6 dB 0 / 0":
<tr />
Name "2단계 SNR +3 dB 0 / 0":
<tr />
Name "3단계 SNR 0 dB 0 / 0":
<tr />
Name "4단계 SNR -3 dB 0 / 0":
<tr />
Name "5단계 SNR -6 dB 0 / 0":
<tr />
--------------------------------------------------
columnheader:
Name "난이도":
<th
scope="col"
/>
Name "SNR":
<th
scope="col"
/>
Name "맞힌 문제":
<th
scope="col"
/>
--------------------------------------------------
rowheader:
Name "1단계":
<th
scope="row"
/>
Name "2단계":
<th
scope="row"
/>
Name "3단계":
<th
scope="row"
/>
Name "4단계":
<th
scope="row"
/>
Name "5단계":
<th
scope="row"
/>
--------------------------------------------------
cell:
Name "SNR +6 dB":
<td />
Name "0 / 0":
<td />
Name "SNR +3 dB":
<td />
Name "0 / 0":
<td />
Name "SNR 0 dB":
<td />
Name "0 / 0":
<td />
Name "SNR -3 dB":
<td />
Name "0 / 0":
<td />
Name "SNR -6 dB":
<td />
Name "0 / 0":
<td />
--------------------------------------------------
button:
Name "처음 화면으로":
<button
class="secondary"
/>
--------------------------------------------------
slider:
Name "전체 음량":
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
--------------------------------------------------
status:
Name "":
<output
for="volume"
/>
--------------------------------------------------
contentinfo:
Name "":
<footer
class="footer wrap"
/>
--------------------------------------------------
Ignored nodes: comments, script, style
<body>
<div>
<a
class="skip-link"
href="#main"
>
본문으로 건너뛰기
</a>
<header
class="header wrap"
>
<div
class="brand"
>
<span
aria-hidden="true"
class="brand-mark"
>
<i />
<i />
<i />
<i />
</span>
<h1>
단어를 맞춰라
</h1>
<span
class="version"
>
v0.2.3
</span>
</div>
<div
class="header-actions"
>
<span
class="little-dot"
/>
소리에 집중하는 시간
</div>
</header>
<main
class="wrap"
id="main"
tabindex="-1"
>
<section
class="results-panel panel"
>
<span
class="eyebrow"
>
지금까지의 소리 탐험
</span>
<div
class="result-mascot"
>
<div
aria-hidden="true"
class="mascot small"
>
<span
class="ear left"
/>
<span
class="ear right"
/>
<span
class="eye left"
/>
<span
class="eye right"
/>
<span
class="smile"
/>
<b>
S
</b>
</div>
</div>
<h2
tabindex="-1"
>
문제 풀이를 종료했어요.
</h2>
<p
class="muted"
>
작은 소리에 집중한 당신, 다음 탐험에서도 만나요.
</p>
<p>
응답 
0
문항 · 정답 
0
문항
</p>
<div
class="stats"
>
<div>
<strong>
0
</strong>
<span>
총 점수
</span>
</div>
<div>
<strong>
0
%
</strong>
<span>
정답률 · 
0
/
0
</span>
</div>
<div>
<strong>
0
<small>
회
</small>
</strong>
<span>
최고 연속 정답
</span>
</div>
</div>
<table
aria-label="난이도별 결과"
>
<thead>
<tr>
<th
scope="col"
>
난이도
</th>
<th
scope="col"
>
SNR
</th>
<th
scope="col"
>
맞힌 문제
</th>
</tr>
</thead>
<tbody>
<tr>
<th
scope="row"
>
1
단계
</th>
<td>
SNR +6 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
2
단계
</th>
<td>
SNR +3 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
3
단계
</th>
<td>
SNR 0 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
4
단계
</th>
<td>
SNR -3 dB
</td>
<td>
0
 / 
0
</td>
</tr>
<tr>
<th
scope="row"
>
5
단계
</th>
<td>
SNR -6 dB
</td>
<td>
0
 / 
0
</td>
</tr>
</tbody>
</table>
<p
class="muted"
>
단계 × 100점 + 연속 정답 보너스 (라운드 내 최대
 
20
점)
</p>
<button
class="secondary"
>
처음 화면으로
</button>
</section>
<div
class="volume-row"
>
<label
for="volume"
>
<span
aria-hidden="true"
>
◖))
</span>
 전체 음량
</label>
<input
id="volume"
max="100"
min="0"
type="range"
value="35"
/>
<output
for="volume"
>
35
%
</output>
</div>
</main>
<footer
class="footer wrap"
>
<p>
Designed by OZ at Heat-T Medical Inc.
</p>
<p>
청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다.
</p>
<span>
개인정보 수집 없이, 오직 플레이만.
</span>
<a
href="/AMBIENCE_NOTICES.md"
>
환경음 출처·라이선스
</a>
</footer>
</div>
</body>
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19
❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38
❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17
❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19
❯ tests/App.test.tsx:538:17
536| await f.user.click(screen.getByRole("button", { name: "게임 시작 · 소리 듣기…
537| await f.user.click(screen.getByRole("button", { name: "문제 풀이 종료" }));
538| expect(screen.getByRole("heading", { name: "문제 풀이를 중간에 종료했어요." })).t…
| ^
539| expect(screen.getByText("—")).toBeInTheDocument();
540| expect(screen.getByText("정답률 · 응답 없음")).toBeInTheDocument();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/8]⎯
FAIL tests/neural-release.test.ts > README는 현재 리뷰 증거 경로와 정확한 라운드 보너스를 안내한다
AssertionError: expected '# 단어를 맞춰라 · Find-Word v0.2.3\n\n소음 속에…' to contain '.agent/evidence-v0.2.3/review-fixes/'
- Expected
+ Received
- .agent/evidence-v0.2.3/review-fixes/
+ # 단어를 맞춰라 · Find-Word v0.2.3
+
+ 소음 속에 숨은 한국어 단어를 듣고 찾는 반응형 웹 게임입니다.
+
+ **Designed by OZ at Heat-T Medical Inc.**
+
+ Canonical repository: [OZ-Group/Find-Word](https://source.ibit.kr/OZ-Group/Find-Word).
+ 프로젝트 자체는 **private / all-rights-reserved**이며 `package.json`의
+ `license`는 `UNLICENSED`입니다. [LICENSE](LICENSE)를 참고하세요.
+ 런타임 의존성 React, ReactDOM, scheduler의 MIT 전문은
+ [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)에 보존하며,
+ `public/THIRD_PARTY_NOTICES.md`가 빌드 시 `dist/THIRD_PARTY_NOTICES.md`로 복사됩니다.
+ 빌드 검증기는 설치된 의존성의 라이선스 전문·버전과 세 사본을 대조합니다.
+
+ React + TypeScript strict + Vite로 만든 정적 SPA입니다. 로그인, 개인정보 저장,
+ 분석 도구, 외부 CDN, 런타임 TTS/API 요청이 없습니다. 청각적 주의·식별 게임이며
+ 의료적 진단·치료나 효과를 주장하지 않습니다.
+
+ ## 실행
+
+ Node.js 최소 버전은 **22.22.2** (`>=22.22.2`)입니다. 잠긴 jsdom 계열 의존성이
+ 요구하는 지원 범위는 22.x의 22.22.2 이상, 24.x의 24.15.0 이상 또는 26.0.0 이상입니다.
+ 이 중 지원되는 LTS 버전을 사용하세요. `package-lock.json`을
+ 포함하므로 새 환경에서는 `npm ci`를 사용합니다. 이번 검증 환경은 Node 26.7.0입니다.
+
+ ```bash
+ npm ci
+ npm run dev
+ ```
+
+ 표시되는 `http://127.0.0.1:5173`에서 사용합니다. 소리는 사용자가 **게임 시작 · 소리 듣기**를
+ 누른 뒤에만 재생합니다. 낮은 음량부터 조절하고 헤드폰이나 스피커를 사용할 수 있습니다.
+
+ ## 플레이
+
+ - 골고루 풀기, 보기에서 선택, 직접 입력 중 선택합니다.
+ - 8개 실제 현장 녹음 환경 중 하나를 지정하거나 라운드마다 섞어 세 곳을 방문합니다.
+ - 에스의 안내가 배경에 묻힌 뒤 목표 문장이 나옵니다. 재생 후 질문에 답합니다.
+ - 시작 난이도 1~5를 고르고 정확히 **3문항**을 같은 단계에서 풉니다. 단계 기본값은 **SNR +6, +3, 0, −3, −6 dB**입니다.
+ - 목표 음성 SNR을 **−12~+12 dB, 1 dB 간격**으로 직접 조절할 수 있습니다. 재생 중 변경은 다음 재생·재청취와 남은 문제부터 적용합니다.
+ 양수는 음성이 배경보다 크고, 0은 같고, 음수는 더 작다는 뜻입니다.
+ - 다시 듣기는 감점하지 않습니다. 정답은 `단계 × 100점`, 연속 정답은 라운드 내 최대 20점의 보너스를 받습니다.
+ - 정상 결과에서 다음 단계의 새 3문항을 바로 시작합니다. 5단계에서는 같은 단계 재도전과 홈 복귀를 제공합니다. 수동 SNR은 다음 라운드에도 유지되고, 홈 복귀 시 선택한 시작 단계의 기본값으로 돌아갑니다.
+ - 언제든 **문제 풀이 종료**를 누르면 즉시 오디오를 취소하고 **응답 수·정답 수**를 보여 줍니다. 무응답은 오답으로 세지 않으며 결과에서 **처음 화면으로** 돌아갑니다.
+ - 결과의 SNR은 실제 마지막 청취값을 기록합니다. 점수·정답률·최고 연속 정답·단계별 결과를 확인합니다.
+ - 새로고침하면 기록이 사라집니다. 결과를 브라우저나 서버에 저장하지 않습니다.
+
+ 10개 유형 × 10개 단어의 100개 목표 문장을 저장했습니다. 각 세션은 유형과
+ 단어를 섞어 출제하며 정답 위치도 섞습니다. 사전 표제어와 변형 규칙은
+ [WORDS.md](docs/WORDS.md)에 설명했습니다.
+ 청취 과제는 직접 목표 식별, 연습 암호와 진짜 암호 구별, 앞 단어를 잊고 다음
+ 단어 기억, 첫째·마지막·가운데 선택, 반복 찾기, 정정 반영, 신호 조건 적용,
+ 오른쪽 표지 선택의 10가지입니다. 문장에 실제 보기의 오답도 포함됩니다.
+
+ ## 검증과 정적 빌드
+
+ ```bash
+ npm run lint
+ npm test
+ python3 -m unittest discover -s tests/python -v
+ npm run build
+ npm audit
+ npm run preview
+ ```
+
+ `lint`는 ESLint와 TypeScript 검사, `test`는 unit/component/자산/빌드 검증기
+ 테스트를 실행합니다. `build`는 `dist/`를 만들고 음성 101개와 현장음 8개가 manifest 해시 그대로
+ 포함되었는지 확인합니다. 빌드에는 음성 합성 도구가 필요하지 않습니다.
+ 런타임 MIT notice와 현장음 attribution/license notice가 `dist/`에 전문 그대로 포함되는지도 검증합니다.
+
+ 실제 오디오·360px/1280px 화면·키보드·접근성·오류 재시도 검증:
+
+ ```bash
+ # Chromium이 없는 개발 환경에서 한 번만 실행합니다.
+ npx playwright install chromium
+ npm run test:browser
+ # 이미 설치된 Chrome을 사용할 때의 대안입니다.
+ CHROME_PATH=/usr/bin/google-chrome npm run test:browser
+ ```
+
+ Chromium sandbox는 기본으로 켭니다. sandbox를 지원하지 않는 격리된 개발
+ 환경에서만 `PLAYWRIGHT_NO_SANDBOX=1`을 명시해 끌 수 있습니다. `0`, `false`,
+ 빈 값이나 미설정은 sandbox를 유지합니다. `CHROME_PATH` 지정은 sandbox를 끄지 않습니다.
+
+ 브라우저 테스트는 기존 `dist/`를 사용하므로 소스 변경 후 먼저 빌드하세요.
+ 실제 음성 재생이 끝나기를 기다려 몇 분이 걸립니다. 로컬 4173 포트를
+ 테스트 중에만 열고 종료합니다. 스크린샷과 109개 WAV의 query URL·해시·디코드
+ 결과는 `test-results/`에 저장합니다. 인수 기록은 실행별 새 evidence 경로에
+ 복사하여 `.agent/evidence-v0.1.1/`의 과거 결과를 보존합니다.
+
+ ## 음성과 문서
+
+ 한국어 WAV 101개는 직접 쓴 문장을 Microsoft Edge online TTS의
+ `ko-KR-HyunsuMultilingualNeural`로 v0.1.1에서 생성했으며 WAV 바이트는 그대로 보존했습니다. edge-tts **7.3.1**,
+ rate `-6%`, pitch `-4Hz`이며 FFmpeg로 **24kHz mono PCM16**으로 변환합니다.
+ 생성 시 네트워크가 필요하며 앱은 저장된 WAV만 재생합니다. 환경음은 공개 라이선스의 실제 현장 녹음 8개를 각 12초로 변환해 번들합니다. 생성 음성의 독점 권리를 주장하지 않습니다.
+
+ intro와 목표 WAV는 모두 `/audio/파일명.wav?v=0.2.3`로 요청합니다.
+ `src/audio/assets.ts`의 자산 버전 한 곳을 사용하며, 같은 파일명으로 음성을
+ 교체할 때 이 값을 갱신해 기존 7일 캐시와 구분합니다. 배경은 `/ambience/환경.wav?v=0.2.3`입니다. 제품 버전은 0.2.3입니다.
+
+ Linux에서 Python 3.11 이상과 FFmpeg를 준비한 뒤 다음 명령을 사용합니다.
+ 클라이언트 버전을 고정해도 원격 모델 변경으로 동일 바이트 재생성은 보장하지 않습니다.
+
+ ```bash
+ python3 -m venv .venv-audio
+ .venv-audio/bin/python -m pip install -r requirements-audio.txt
+ .venv-audio/bin/python -m pip check
+ npm run audio:generate
+ ```
+
+ 고정 venv의 Python이 101개 모두 임시 생성·검증하고 디렉터리를 원자 교체합니다.
+ 요청별 60초 제한과 최대 3회 시도를 적용하며, 실패 시 기존 자산을 보존합니다.
+ 청취 샘플: [안내](public/audio/intro.wav), [행운의 사과](public/audio/luck-apple.wav).
+ 생성 방법, 도구 라이선스, 합성 음질의 한계는 [AUDIO_PROVENANCE.md](docs/AUDIO_PROVENANCE.md)를
+ 참고하세요. 실제 음량은 출력 장치에 따라 달라지며 표기 dB는 디지털 상대값입니다.
+
+ 현장음 원본은 `/tmp/findword-ambience-sources`에서만 보관하고 배포하지 않습니다.
+ `python3 scripts/ambience_audio.py`로 검증된 원본의 파생본과 manifest를 재현합니다.
+ PD·CC0·CC BY 4.0·CC BY-SA 4.0의 정확한 출처·저작자·변환·해시는
+ [현장음 manifest](public/ambience/manifest.json), [배포 고지](public/AMBIENCE_NOTICES.md)에 있습니다.
+ 시장 파생본은 CC BY-SA 4.0을 유지합니다. 외부 링크는 출처 확인용이며 앱은 이를 요청하지 않습니다.
+
+ - [변경 이력](CHANGELOG.md)
+ - [v0.2.3 구현·검증 결과](.agent/RESULT-v0.2.3.md)
+ - [설계와 상태 전이](docs/ARCHITECTURE.md)
+ - [Neo를 위한 정적 배포 안내](docs/DEPLOYMENT.md)
+ - [v0.1.1 RED/GREEN 및 실제 검증 결과](.agent/RESULT-v0.1.1.md)
+ - [v0.1 역사 기록](.agent/RESULT.md)
+
+ 이번 작업에서는 서버 배포와 Git commit/push를 실행하지 않습니다.
+
❯ tests/neural-release.test.ts:72:20
70| it("README는 현재 리뷰 증거 경로와 정확한 라운드 보너스를 안내한다", () => {
71| const doc = readFileSync("README.md", "utf8");
72| expect.soft(doc).toContain(".agent/evidence-v0.2.3/review-fixes/");
| ^
73| expect.soft(doc).toContain("문항별 최대 20점, 라운드 합계 최대 30점");
74| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/8]⎯
FAIL tests/neural-release.test.ts > README는 현재 리뷰 증거 경로와 정확한 라운드 보너스를 안내한다
AssertionError: expected '# 단어를 맞춰라 · Find-Word v0.2.3\n\n소음 속에…' to contain '문항별 최대 20점, 라운드 합계 최대 30점'
- Expected
+ Received
- 문항별 최대 20점, 라운드 합계 최대 30점
+ # 단어를 맞춰라 · Find-Word v0.2.3
+
+ 소음 속에 숨은 한국어 단어를 듣고 찾는 반응형 웹 게임입니다.
+
+ **Designed by OZ at Heat-T Medical Inc.**
+
+ Canonical repository: [OZ-Group/Find-Word](https://source.ibit.kr/OZ-Group/Find-Word).
+ 프로젝트 자체는 **private / all-rights-reserved**이며 `package.json`의
+ `license`는 `UNLICENSED`입니다. [LICENSE](LICENSE)를 참고하세요.
+ 런타임 의존성 React, ReactDOM, scheduler의 MIT 전문은
+ [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)에 보존하며,
+ `public/THIRD_PARTY_NOTICES.md`가 빌드 시 `dist/THIRD_PARTY_NOTICES.md`로 복사됩니다.
+ 빌드 검증기는 설치된 의존성의 라이선스 전문·버전과 세 사본을 대조합니다.
+
+ React + TypeScript strict + Vite로 만든 정적 SPA입니다. 로그인, 개인정보 저장,
+ 분석 도구, 외부 CDN, 런타임 TTS/API 요청이 없습니다. 청각적 주의·식별 게임이며
+ 의료적 진단·치료나 효과를 주장하지 않습니다.
+
+ ## 실행
+
+ Node.js 최소 버전은 **22.22.2** (`>=22.22.2`)입니다. 잠긴 jsdom 계열 의존성이
+ 요구하는 지원 범위는 22.x의 22.22.2 이상, 24.x의 24.15.0 이상 또는 26.0.0 이상입니다.
+ 이 중 지원되는 LTS 버전을 사용하세요. `package-lock.json`을
+ 포함하므로 새 환경에서는 `npm ci`를 사용합니다. 이번 검증 환경은 Node 26.7.0입니다.
+
+ ```bash
+ npm ci
+ npm run dev
+ ```
+
+ 표시되는 `http://127.0.0.1:5173`에서 사용합니다. 소리는 사용자가 **게임 시작 · 소리 듣기**를
+ 누른 뒤에만 재생합니다. 낮은 음량부터 조절하고 헤드폰이나 스피커를 사용할 수 있습니다.
+
+ ## 플레이
+
+ - 골고루 풀기, 보기에서 선택, 직접 입력 중 선택합니다.
+ - 8개 실제 현장 녹음 환경 중 하나를 지정하거나 라운드마다 섞어 세 곳을 방문합니다.
+ - 에스의 안내가 배경에 묻힌 뒤 목표 문장이 나옵니다. 재생 후 질문에 답합니다.
+ - 시작 난이도 1~5를 고르고 정확히 **3문항**을 같은 단계에서 풉니다. 단계 기본값은 **SNR +6, +3, 0, −3, −6 dB**입니다.
+ - 목표 음성 SNR을 **−12~+12 dB, 1 dB 간격**으로 직접 조절할 수 있습니다. 재생 중 변경은 다음 재생·재청취와 남은 문제부터 적용합니다.
+ 양수는 음성이 배경보다 크고, 0은 같고, 음수는 더 작다는 뜻입니다.
+ - 다시 듣기는 감점하지 않습니다. 정답은 `단계 × 100점`, 연속 정답은 라운드 내 최대 20점의 보너스를 받습니다.
+ - 정상 결과에서 다음 단계의 새 3문항을 바로 시작합니다. 5단계에서는 같은 단계 재도전과 홈 복귀를 제공합니다. 수동 SNR은 다음 라운드에도 유지되고, 홈 복귀 시 선택한 시작 단계의 기본값으로 돌아갑니다.
+ - 언제든 **문제 풀이 종료**를 누르면 즉시 오디오를 취소하고 **응답 수·정답 수**를 보여 줍니다. 무응답은 오답으로 세지 않으며 결과에서 **처음 화면으로** 돌아갑니다.
+ - 결과의 SNR은 실제 마지막 청취값을 기록합니다. 점수·정답률·최고 연속 정답·단계별 결과를 확인합니다.
+ - 새로고침하면 기록이 사라집니다. 결과를 브라우저나 서버에 저장하지 않습니다.
+
+ 10개 유형 × 10개 단어의 100개 목표 문장을 저장했습니다. 각 세션은 유형과
+ 단어를 섞어 출제하며 정답 위치도 섞습니다. 사전 표제어와 변형 규칙은
+ [WORDS.md](docs/WORDS.md)에 설명했습니다.
+ 청취 과제는 직접 목표 식별, 연습 암호와 진짜 암호 구별, 앞 단어를 잊고 다음
+ 단어 기억, 첫째·마지막·가운데 선택, 반복 찾기, 정정 반영, 신호 조건 적용,
+ 오른쪽 표지 선택의 10가지입니다. 문장에 실제 보기의 오답도 포함됩니다.
+
+ ## 검증과 정적 빌드
+
+ ```bash
+ npm run lint
+ npm test
+ python3 -m unittest discover -s tests/python -v
+ npm run build
+ npm audit
+ npm run preview
+ ```
+
+ `lint`는 ESLint와 TypeScript 검사, `test`는 unit/component/자산/빌드 검증기
+ 테스트를 실행합니다. `build`는 `dist/`를 만들고 음성 101개와 현장음 8개가 manifest 해시 그대로
+ 포함되었는지 확인합니다. 빌드에는 음성 합성 도구가 필요하지 않습니다.
+ 런타임 MIT notice와 현장음 attribution/license notice가 `dist/`에 전문 그대로 포함되는지도 검증합니다.
+
+ 실제 오디오·360px/1280px 화면·키보드·접근성·오류 재시도 검증:
+
+ ```bash
+ # Chromium이 없는 개발 환경에서 한 번만 실행합니다.
+ npx playwright install chromium
+ npm run test:browser
+ # 이미 설치된 Chrome을 사용할 때의 대안입니다.
+ CHROME_PATH=/usr/bin/google-chrome npm run test:browser
+ ```
+
+ Chromium sandbox는 기본으로 켭니다. sandbox를 지원하지 않는 격리된 개발
+ 환경에서만 `PLAYWRIGHT_NO_SANDBOX=1`을 명시해 끌 수 있습니다. `0`, `false`,
+ 빈 값이나 미설정은 sandbox를 유지합니다. `CHROME_PATH` 지정은 sandbox를 끄지 않습니다.
+
+ 브라우저 테스트는 기존 `dist/`를 사용하므로 소스 변경 후 먼저 빌드하세요.
+ 실제 음성 재생이 끝나기를 기다려 몇 분이 걸립니다. 로컬 4173 포트를
+ 테스트 중에만 열고 종료합니다. 스크린샷과 109개 WAV의 query URL·해시·디코드
+ 결과는 `test-results/`에 저장합니다. 인수 기록은 실행별 새 evidence 경로에
+ 복사하여 `.agent/evidence-v0.1.1/`의 과거 결과를 보존합니다.
+
+ ## 음성과 문서
+
+ 한국어 WAV 101개는 직접 쓴 문장을 Microsoft Edge online TTS의
+ `ko-KR-HyunsuMultilingualNeural`로 v0.1.1에서 생성했으며 WAV 바이트는 그대로 보존했습니다. edge-tts **7.3.1**,
+ rate `-6%`, pitch `-4Hz`이며 FFmpeg로 **24kHz mono PCM16**으로 변환합니다.
+ 생성 시 네트워크가 필요하며 앱은 저장된 WAV만 재생합니다. 환경음은 공개 라이선스의 실제 현장 녹음 8개를 각 12초로 변환해 번들합니다. 생성 음성의 독점 권리를 주장하지 않습니다.
+
+ intro와 목표 WAV는 모두 `/audio/파일명.wav?v=0.2.3`로 요청합니다.
+ `src/audio/assets.ts`의 자산 버전 한 곳을 사용하며, 같은 파일명으로 음성을
+ 교체할 때 이 값을 갱신해 기존 7일 캐시와 구분합니다. 배경은 `/ambience/환경.wav?v=0.2.3`입니다. 제품 버전은 0.2.3입니다.
+
+ Linux에서 Python 3.11 이상과 FFmpeg를 준비한 뒤 다음 명령을 사용합니다.
+ 클라이언트 버전을 고정해도 원격 모델 변경으로 동일 바이트 재생성은 보장하지 않습니다.
+
+ ```bash
+ python3 -m venv .venv-audio
+ .venv-audio/bin/python -m pip install -r requirements-audio.txt
+ .venv-audio/bin/python -m pip check
+ npm run audio:generate
+ ```
+
+ 고정 venv의 Python이 101개 모두 임시 생성·검증하고 디렉터리를 원자 교체합니다.
+ 요청별 60초 제한과 최대 3회 시도를 적용하며, 실패 시 기존 자산을 보존합니다.
+ 청취 샘플: [안내](public/audio/intro.wav), [행운의 사과](public/audio/luck-apple.wav).
+ 생성 방법, 도구 라이선스, 합성 음질의 한계는 [AUDIO_PROVENANCE.md](docs/AUDIO_PROVENANCE.md)를
+ 참고하세요. 실제 음량은 출력 장치에 따라 달라지며 표기 dB는 디지털 상대값입니다.
+
+ 현장음 원본은 `/tmp/findword-ambience-sources`에서만 보관하고 배포하지 않습니다.
+ `python3 scripts/ambience_audio.py`로 검증된 원본의 파생본과 manifest를 재현합니다.
+ PD·CC0·CC BY 4.0·CC BY-SA 4.0의 정확한 출처·저작자·변환·해시는
+ [현장음 manifest](public/ambience/manifest.json), [배포 고지](public/AMBIENCE_NOTICES.md)에 있습니다.
+ 시장 파생본은 CC BY-SA 4.0을 유지합니다. 외부 링크는 출처 확인용이며 앱은 이를 요청하지 않습니다.
+
+ - [변경 이력](CHANGELOG.md)
+ - [v0.2.3 구현·검증 결과](.agent/RESULT-v0.2.3.md)
+ - [설계와 상태 전이](docs/ARCHITECTURE.md)
+ - [Neo를 위한 정적 배포 안내](docs/DEPLOYMENT.md)
+ - [v0.1.1 RED/GREEN 및 실제 검증 결과](.agent/RESULT-v0.1.1.md)
+ - [v0.1 역사 기록](.agent/RESULT.md)
+
+ 이번 작업에서는 서버 배포와 Git commit/push를 실행하지 않습니다.
+
❯ tests/neural-release.test.ts:73:20
71| const doc = readFileSync("README.md", "utf8");
72| expect.soft(doc).toContain(".agent/evidence-v0.2.3/review-fixes/");
73| expect.soft(doc).toContain("문항별 최대 20점, 라운드 합계 최대 30점");
| ^
74| });
75|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/8]⎯
Test Files 2 failed (2)
Tests 7 failed | 20 passed (27)
Start at 08:46:34
Duration 7.76s (tests 81%, environment 12%, setup 4%, transform 2%, import 1%)
EXIT_CODE=1