feat: release Find-Word v0.1
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# 설계
|
||||
|
||||
## 데이터와 출제
|
||||
|
||||
`src/content.ts`는 패턴, 질문, 단어, 오답 규칙, 배경 프리셋, 상대 dB를 서로 다른
|
||||
배열로 보관한다. `renderSentence`가 각 패턴의 `{word}`를 정답으로,
|
||||
`{distractor1}`·`{distractor2}`를 해당 보기의 오답으로 채운다. 같은 함수를
|
||||
음성 생성기도 사용하며 런타임 문장과 WAV의 대응은 manifest 테스트가 검증한다.
|
||||
|
||||
`Pattern.mechanic`은 다음 10개 고유 청취 연산을 나타낸다. 인용 단어를 활용형으로
|
||||
바꾸지 않으므로 동사·부사 표제어가 오답으로 나와도 문장이 자연스럽다.
|
||||
|
||||
| mechanic | 청취 과제 |
|
||||
| --- | --- |
|
||||
| `direct-target` | 한 개의 목표 단어를 직접 식별 |
|
||||
| `distractor-password` | 연습 암호를 제외하고 진짜 암호 선택 |
|
||||
| `ignore-then-remember` | 앞 단어를 잊고 이후에 지시된 단어 기억 |
|
||||
| `first` | 세 단어 중 처음 선택 |
|
||||
| `last` | 세 단어 중 마지막 선택 |
|
||||
| `middle` | 세 단어 중 가운데 선택 |
|
||||
| `repetition` | 오답 사이에서 두 번 등장한 단어 찾기 |
|
||||
| `correction` | 처음 답을 취소하고 정정된 답 선택 |
|
||||
| `conditional-cue` | 초록/빨강 규칙과 현재 신호를 함께 적용 |
|
||||
| `spatial-right-cue` | 좌우 표지 중 오른쪽에 해당하는 단어 선택 |
|
||||
|
||||
방향 단서는 문장의 의미이며 stereo 위치 재현 과제는 아니다. 모든 음성은 mono다.
|
||||
직접 식별을 제외한 문장은 실제 보기의 경쟁 단어도 발화하므로, 들린 단어 하나를
|
||||
무조건 고르는 것으로 해결되지 않는다. 테스트는 100개 문장 전체에서 단어 순서,
|
||||
목표 반복 횟수, 취소·조건·방향 단서와 질문 의도를 검증한다.
|
||||
|
||||
`src/game.ts`의 `createQuestions`는 seed 기반 PRNG와 Fisher–Yates로
|
||||
유형·단어·정답 위치를 섞는다. 한 세션에서 유형과 정답 단어는 각각 한 번씩
|
||||
나온다. 100개 문장 조합에 응답 방식·환경·난이도를 독립적으로 결합한다.
|
||||
|
||||
오답의 `rule`은 `RULE_DIFFICULTY`에 연결된다. 음절 교체 1, 초성 교체 2,
|
||||
모음 교체 3, 받침 교체 4는 편집상 예상 혼동 순서이며 검증된 청각 척도가
|
||||
아니다. 모든 문항에 서로 다른 세 변형을 포함한다. 음량 단계는 이 값과 분리된다.
|
||||
|
||||
## 게임 상태
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> ready
|
||||
ready --> loading: PLAY
|
||||
loading --> playing: STARTED
|
||||
loading --> ready: ERROR
|
||||
playing --> ready: ERROR / 중단
|
||||
playing --> answer: ENDED
|
||||
answer --> loading: 다시 듣기
|
||||
answer --> feedback: ANSWER
|
||||
feedback --> ready: NEXT (다음 문제)
|
||||
feedback --> results: NEXT (마지막 문제)
|
||||
```
|
||||
|
||||
허용되지 않은 이벤트는 동일 상태를 반환한다. 재생 전/중에는 채점할 수 없고,
|
||||
피드백 이후의 중복 답변·다음 클릭도 한 번만 반영된다. UI에서도 버튼 비활성화와
|
||||
동기 ref 잠금을 함께 사용한다. 세션 토큰으로 처음 화면 복귀·unmount 뒤 완료된
|
||||
Promise를 무시한다. 질문과 보기는 재생 완료 후 DOM에 생성되며 정답 문장은
|
||||
채점 후에만 표시된다.
|
||||
|
||||
주관식은 NFC 한글 정규화 후 공백과 유니코드 문장부호만 제거한다. 비어 있는
|
||||
답변과 부분 일치는 정답으로 인정하지 않는다. React 텍스트 노드로만 출력하여
|
||||
입력을 HTML로 해석하지 않는다.
|
||||
|
||||
점수는 정답 시 `100 × 단계 + 10 × min(연속 정답 - 1, 4)`이다. 오답은 0점,
|
||||
현재 연속 정답을 초기화한다. 최고 연속 정답과 난이도별 정답 수는 유지한다.
|
||||
|
||||
## 오디오
|
||||
|
||||
`src/audio/dsp.ts`는 순수 수치 함수이며 장치가 없어도 테스트할 수 있다.
|
||||
정수 seed 잡음에 저역 통과 필터, 사인파, 진폭 변조를 조합하고 양 끝을 페이드한다.
|
||||
8개 프리셋은 실제 현장 녹음이 아니라 환경을 추상적으로 표현한 합성 신호다.
|
||||
|
||||
`src/audio/engine.ts`는 클릭 이벤트에서 AudioContext 생성·resume을 호출한 뒤
|
||||
같은 출처의 WAV를 로드한다. mono PCM을 decode하고 음성과 배경의 전체 버퍼
|
||||
RMS를 0.03으로 정규화한다. `gain = 10^(dB/20)`으로 목표 gain을 지정한다.
|
||||
시간별·주파수별 청감이나 물리적 dB SPL을 보장하는 수치는 아니다.
|
||||
|
||||
```text
|
||||
배경 BufferSource → 배경 Gain (1, 시작/끝 fade) ─┐
|
||||
목표 BufferSource → 음성 Gain (10^(dB/20)) ─────┼→ master Gain → destination
|
||||
안내 BufferSource → 안내 Gain (1.8 → 0.12) ─────┘
|
||||
```
|
||||
|
||||
첫 문제에서는 웃음과 안내 음성을 낮추어 배경에 묻히게 한 후 목표 문장을
|
||||
재생한다. 이후에는 목표만 재생한다. 안내 도중 실패했으면 재시도 시 안내도
|
||||
다시 재생한다. 기본 master는 35%이며 음량 조절은 두 신호의 비율을 유지한다.
|
||||
|
||||
동시 play는 거부한다. 정지·실패·완료는 onended 해제, 소스 stop/disconnect,
|
||||
AbortController 취소, 타이머 제거를 수행한다. load/decode 뒤에도 작업 토큰을
|
||||
검사하여 취소된 이전 작업이 소리를 시작하지 못하게 한다. 준비 15초,
|
||||
재생 길이 + 5초의 제한으로 무한 대기를 피한다. unmount는 context까지 닫는다.
|
||||
|
||||
## 화면·접근성·보안
|
||||
|
||||
`App.tsx`는 시작·플레이·결과 화면, 음량, 모드, 배경 선택을 담당한다.
|
||||
정답/피드백/결과 제목으로 초점을 이동하고 결과표에 caption에 해당하는
|
||||
접근성 이름과 scope를 둔다. 네이티브 radio/select/button/form으로 방향키,
|
||||
Tab/Enter와 터치 조작을 지원한다. 장식 파형·캐릭터는 aria-hidden이며
|
||||
reduced motion에서는 파형이 움직이지 않는다. 오류는 role=alert로 전달한다.
|
||||
|
||||
외부 폰트, CDN, 이미지, 분석 도구, 로그인, 쿠키, localStorage 사용이 없다.
|
||||
개인 입력과 점수는 현재 React 메모리에만 존재한다. 단어·정답은 클라이언트
|
||||
번들에 있으므로 개발자 도구를 통한 정답 열람을 막는 시험 시스템은 아니다.
|
||||
|
||||
## 검증 경계
|
||||
|
||||
Vitest는 실제 RED/GREEN으로 데이터·채점·상태·오디오 그래프·화면을 검증한다.
|
||||
Playwright는 production build와 실제 Chrome AudioContext로 두 크기의 10문제
|
||||
완주·재청취·오류 복구·axe 검사를 수행한다. 테스트 자동화는 한국어 발음의
|
||||
자연스러움, 실제 사용자의 청각 난이도, 모바일 Safari의 장치별 정책까지
|
||||
증명하지 않으므로 인수 시 직접 청취를 추가한다.
|
||||
@@ -0,0 +1,83 @@
|
||||
# 오디오 출처와 재현
|
||||
|
||||
## 포함 자산
|
||||
|
||||
| 대상 | 생성 방식 | 원문/설정 | 배포 방식 |
|
||||
| --- | --- | --- | --- |
|
||||
| `public/audio/intro.wav` | 로컬 eSpeak NG 한국어 포먼트 합성 | `src/content.ts`의 INTRO | PCM WAV |
|
||||
| 목표 음성 100개 | 같은 로컬 엔진 | 10 패턴 × 10 단어 | PCM WAV |
|
||||
| 길거리·쇼핑몰·극장·백화점·시장·지하철·카페·비 오는 거리 | 직접 작성한 결정론적 DSP | `ENVIRONMENTS`, 고정 seed | 브라우저 메모리에서 합성 |
|
||||
| 화면 파형·S 캐릭터 | 직접 작성한 CSS | `App.tsx`, `styles.css` | CSS, 외부 이미지 없음 |
|
||||
|
||||
모든 한국어 문장은 이번 앱을 위해 직접 작성했다. 다른 사람의 녹음, 노래,
|
||||
샘플 라이브러리, 음성 복제, 신경망 모델을 포함하지 않는다. 웃음도 INTRO의
|
||||
‘으하하하~~’ 문자열을 동일 엔진으로 합성한 것이다. 효과를 위해 타인 음원을
|
||||
다운로드하지 않았다. 런타임에 speechSynthesis 또는 네트워크 TTS도 호출하지 않는다.
|
||||
|
||||
WAV는 16kHz, mono, signed 16-bit little-endian PCM이다. 101개 합계는
|
||||
**20,497,222바이트**다. 파일별 원문·바이트 수·SHA-256은
|
||||
`public/audio/manifest.json`에 있다. 생성 시 실행 도구에서 읽은 eSpeak NG 1.51,
|
||||
FFmpeg 6.1.1-3ubuntu5의 실제 버전도 기록했다. `tests/assets.test.ts`는 원문 대응, PCM
|
||||
포맷, 비무음 신호, 20MiB 이내 크기, gain 적용 후 피크 여유를 검사한다.
|
||||
빌드 마지막에는 `dist/audio` 파일의 해시도 확인한다.
|
||||
청취 연산 변경에 맞춰 101개 WAV와 manifest를 모두 재생성했다. 정답과 경쟁
|
||||
단어는 `renderSentence`로 채우며, 순서·반복·정정·조건·방향 단서가 실제 음성에 들어간다.
|
||||
|
||||
## 사용 도구와 라이선스
|
||||
|
||||
- [eSpeak NG 공식 저장소](https://github.com/espeak-ng/espeak-ng): GPL-3.0-or-later.
|
||||
이번에는 Ubuntu 24.04 패키지 `1.51+dfsg-12build1`, `ko` 포먼트 음성을 사용했다.
|
||||
[공식 언어 목록](https://github.com/espeak-ng/espeak-ng/blob/master/docs/languages.md)에 한국어가 있다.
|
||||
- [FFmpeg 공식 라이선스 안내](https://ffmpeg.org/legal.html): 빌드 구성에 따라
|
||||
LGPL/GPL. 생성 과정에서 PCM 리샘플링만 사용했으며 실행 파일을 배포하지 않는다.
|
||||
- 합성 엔진의 프로그램 라이선스와 그 출력물은 구분한다.
|
||||
[GPLv3 §2](https://www.gnu.org/licenses/gpl-3.0.html#section2) 및
|
||||
[GNU 출력물 설명](https://www.gnu.org/licenses/gpl-faq.html#GPLOutput)에 따르면
|
||||
출력이 그 프로그램을 바탕으로 한 저작물인 경우에만 프로그램 라이선스가
|
||||
출력에 적용된다. 이 작업의 산출물은 직접 작성한 텍스트의 포먼트 합성 파형이며,
|
||||
프로그램 코드·외부 녹음·모델을 포함하지 않는 자체 생성 오디오로 취급한다.
|
||||
- 엔진·라이브러리·다운로드한 Debian 패키지는 `.cache/`에서만 생성 도구로
|
||||
사용하며 Git 대상과 `dist/`에 들어가지 않는다. 별도 외부 음원 라이선스는 없다.
|
||||
프로젝트 자체의 소스·문서·자체 생성 자산은 private / all-rights-reserved다.
|
||||
[루트 LICENSE](../LICENSE)를 따른다. 별도로 번들에 포함되는 React, ReactDOM,
|
||||
scheduler의 MIT 전문은 [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md)에
|
||||
있으며 `dist/THIRD_PARTY_NOTICES.md`로 함께 배포한다.
|
||||
|
||||
## 생성 절차
|
||||
|
||||
일반적인 Ubuntu 개발 환경에는 `espeak-ng`, `ffmpeg`, Python 3, Node.js가
|
||||
필요하다. **이미 포함된 WAV로 빌드·실행할 때는 이 도구가 필요 없다.**
|
||||
|
||||
```bash
|
||||
npm run audio:generate
|
||||
npm test -- tests/assets.test.ts
|
||||
npm run build
|
||||
```
|
||||
|
||||
현재 작업에서는 시스템 설치 없이 다음 명령으로 worktree 내부에 도구를 풀었다.
|
||||
이 명령은 문서화용이며 서버에서 실행한 이력이 없다.
|
||||
|
||||
```bash
|
||||
mkdir -p .cache/espeak
|
||||
cd .cache/espeak
|
||||
apt download espeak-ng espeak-ng-data libespeak-ng1 libpcaudio0 libsonic0
|
||||
for pkg in *.deb; do dpkg-deb -x "$pkg" root; done
|
||||
cd ../..
|
||||
npm run audio:generate
|
||||
```
|
||||
|
||||
`scripts/generate_audio.py`가 시스템 엔진 또는 `.cache/espeak/root/usr`를
|
||||
자동 선택한다. `ko`, 속도 145, 피치 45, 진폭 100을 고정하고 FFmpeg로 16kHz
|
||||
PCM을 만든 뒤 Python wave 모듈로 메타데이터 없는 WAV를 쓴다. 런타임 데이터는
|
||||
Node의 type stripping으로 읽어 텍스트 중복을 피한다. 같은 엔진·버전·플랫폼에서는
|
||||
해시로 재현 여부를 비교할 수 있다. 다른 버전은 새 출처와 해시를 검토해야 한다.
|
||||
|
||||
## 음량·품질 한계
|
||||
|
||||
보이스와 배경은 같은 디지털 RMS로 정규화되고 음성 경로만 dB gain을 적용한다.
|
||||
안내의 gain은 1.8에서 0.12로 감소한다. 기본 전체 음량은 35%이다.
|
||||
물리적 음압을 측정하거나 장치 출력을 보정하지 않는다.
|
||||
|
||||
포먼트 합성 특성상 한국어 발음·억양과 웃음이 기계적으로 들린다. 배경도 실제
|
||||
장소 녹음처럼 세밀하지 않다. 객관식의 자모 차이와 단어 친숙도, 발음의 자연스러움은
|
||||
검수자가 직접 들어 평가해야 한다. 현재 단계 수치는 임상 검증이나 청력 판정용이 아니다.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 정적 배포 인계
|
||||
|
||||
이 문서는 **Neo가 검토 후 수행할 절차**다. Codex는 서버, DNS, 인증정보에
|
||||
접근하지 않았고 배포·서비스 생성·Git commit/push도 하지 않았다.
|
||||
|
||||
## 산출물
|
||||
|
||||
Node.js 최소 **22.22.2** (`>=22.22.2`)의 지원 LTS 환경에서 `npm ci`, `npm run lint`, `npm test`,
|
||||
`npm run build`를 실행한다. `dist/` 전체를 하나의 릴리스로 전달한다.
|
||||
`dist/audio/`의 WAV 101개와 manifest가 반드시 포함되어야 한다. 런타임은
|
||||
Nginx 정적 파일만 필요하며 Node 서버·TTS 엔진·DB·환경변수·API 키는 필요 없다.
|
||||
잠긴 jsdom 계열 의존성 때문에 24.x는 24.15.0 이상, 26.x는 26.0.0 이상이어야 한다.
|
||||
브라우저 검증의 Chromium sandbox는 기본 활성화다. sandbox를 지원하지 않는
|
||||
격리된 개발 환경에서만 `PLAYWRIGHT_NO_SANDBOX=1`로 명시적으로 끈다.
|
||||
|
||||
Canonical repository는 [OZ-Group/Find-Word](https://source.ibit.kr/OZ-Group/Find-Word)다.
|
||||
프로젝트 자체는 private / all-rights-reserved이며 [LICENSE](../LICENSE)를 따른다.
|
||||
React, ReactDOM, scheduler의 MIT 전문을 담은 `dist/THIRD_PARTY_NOTICES.md`도
|
||||
반드시 배포한다. `build` 검증기는 고지 파일이 없거나 축약·변조되면 실패한다.
|
||||
|
||||
현재 앱은 도메인 루트(`/`) 배포 기준이다. 하위 경로 배포 시 Vite base뿐 아니라
|
||||
음성 URL도 함께 변경하고 테스트해야 한다. 예정 도메인은 `findword.heart-m.com`이다.
|
||||
|
||||
## Nginx 예시
|
||||
|
||||
아래 root는 예시다. 실제 경로·기존 서버 블록·인증서는 Neo가 확인한다.
|
||||
기존 서비스 설정을 덮어쓰지 않고 검토 후 적용한다.
|
||||
|
||||
```nginx
|
||||
# 정적 릴리스 디렉터리를 가리킨다. 인증서와 HTTPS 설정은 운영자가 별도 추가한다.
|
||||
server {
|
||||
listen 80;
|
||||
server_name findword.heart-m.com;
|
||||
root /var/www/findword/current;
|
||||
index index.html;
|
||||
|
||||
# 기본 MIME 정의로 JS/CSS/WAV가 올바른 타입으로 제공되게 한다.
|
||||
include /etc/nginx/mime.types;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Referrer-Policy no-referrer always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
|
||||
|
||||
# 파일명이 해시로 바뀌는 번들만 장기 캐시한다.
|
||||
location /assets/ {
|
||||
try_files $uri =404;
|
||||
expires 1y;
|
||||
}
|
||||
|
||||
# 음성 파일명은 재생성해도 같으므로 재검증한다. 누락 시 index.html 대신
|
||||
# 404를 반환해야 앱이 명확한 음성 로드 오류와 재시도를 제공할 수 있다.
|
||||
location /audio/ {
|
||||
try_files $uri =404;
|
||||
expires -1;
|
||||
}
|
||||
|
||||
# SPA의 직접 진입·새로고침을 index.html로 받되 HTML은 재검증한다.
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
expires -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 인수 확인
|
||||
|
||||
1. 서버에 적용하기 전 `nginx -t`, 기존 서비스 충돌, 파일 읽기 권한을 확인한다.
|
||||
2. TLS/HTTPS와 DNS를 운영자가 확인한다. 실제 도메인에서 시작 후 음성이 들리는지 확인한다.
|
||||
3. `audio/intro.wav`와 목표 WAV가 정상 응답하고 존재하지 않는 오디오는 404인지 확인한다.
|
||||
4. 모바일 Safari/Chrome, 데스크톱에서 음량·중단·다시 듣기·10문제 완주를 직접 확인한다.
|
||||
5. 원자적으로 릴리스 디렉터리를 전환하고 이전 디렉터리를 보관해 되돌릴 수 있게 한다.
|
||||
|
||||
오프라인 캐시를 제공하는 PWA는 아니다. 정적 자산의 최초 로드에는 해당 서버
|
||||
연결이 필요하다. 페이지·오디오 캐시가 서로 다른 버전으로 섞이지 않게 HTML과
|
||||
음성의 재검증 정책을 유지한다.
|
||||
@@ -0,0 +1,40 @@
|
||||
# 단어와 오답 변형
|
||||
|
||||
임의의 한글 조합을 자동 생성하지 않고 사전에 있는 표제어만 고른다. 다음의
|
||||
짧은 의미 구분은 앱 작성자가 작성한 설명이며 사전의 정의·예문·음성을
|
||||
복제한 데이터셋이 아니다. 표기 단어는 `src/content.ts`에 명시적으로 저장한다.
|
||||
|
||||
| 정답 | 초성 교체 | 모음 교체 | 세 번째 변형 |
|
||||
| --- | --- | --- | --- |
|
||||
| 사과 | 다과 (차와 간식) | 사고 (뜻밖의 사건) | 사자 (동물), 음절 교체 |
|
||||
| 바다 | 사다 (구매하다) | 보다 (눈으로 살피다) | 받다 (수령하다), 받침 추가 |
|
||||
| 나무 | 나루 (배가 오가는 곳) | 너무 (정도가 지나치게) | 나물 (먹는 풀), 받침 추가 |
|
||||
| 모자 | 포자 (생물의 번식 단위) | 모조 (원본을 본뜸) | 목자 (양을 돌보는 사람), 받침 추가 |
|
||||
| 우산 | 부산 (도시 이름) | 오산 (잘못된 계산) | 우상 (숭배의 대상), 받침 교체 |
|
||||
| 시장 | 지장 (일을 방해하는 사정) | 사장 (회사 책임자) | 신장 (몸의 키), 받침 추가 |
|
||||
| 기름 | 이름 (대상을 부르는 말) | 거름 (식물에 주는 영양) | 기틀 (기본 구조), 음절 교체 |
|
||||
| 소리 | 오리 (새) | 수리 (고침) | 소금 (조미료), 음절 교체 |
|
||||
| 고기 | 보기 (예시) | 고개 (목 위의 머리) | 공기 (대기), 받침 추가 |
|
||||
| 가구 | 하구 (강이 바다와 만나는 곳) | 가교 (연결하는 다리) | 강구 (방안을 찾음), 받침 추가 |
|
||||
|
||||
동사·부사는 사전 표제어 형태를 사용하고 부산은 사전에 등재된 고유명사로
|
||||
사용한다. 문장의 의미 적합성을 오답 생성 규칙으로 삼지 않고 **들은 낱말의
|
||||
소리 차이**를 묻는다. 이는 의미상 동의어가 아니라 음운상 비슷한 오답이다.
|
||||
|
||||
참조 사전은 국립국어원의 [한국어기초사전](https://krdict.korean.go.kr/kor/mainAction),
|
||||
[표준국어대사전](https://stdict.korean.go.kr/main/main.do),
|
||||
[우리말샘](https://opendict.korean.go.kr/)이다. 혼동하기 쉬운 일부 표제어의
|
||||
직접 확인 링크는 [하구](https://krdict.korean.go.kr/kor/dicSearch/SearchView?ParaWordNo=85229),
|
||||
[목자](https://krdict.korean.go.kr/eng/dicSearch/SearchView?ParaWordNo=55879),
|
||||
[부산](https://krdict.korean.go.kr/kor/dicSearch/SearchView?ParaWordNo=68895)이다.
|
||||
[국립국어원의 나루 설명](https://www.korean.go.kr/front/mcfaq/mcfaqView.do?mcfaq_seq=8815)도 참고한다.
|
||||
앱 실행 중에는 사전에 접속하지 않는다.
|
||||
|
||||
`tests/content.test.ts`는 한글 완성형을 초성·중성·종성으로 분해한다. 초성·모음·받침
|
||||
변형은 정확히 한 자모만 바뀌는지 확인한다. 음절 교체는 더 큰 차이를 허용한다.
|
||||
받침 추가도 종성 0에서 다른 값으로 바뀌는 ‘받침 교체’로 분류한다.
|
||||
|
||||
`RULE_DIFFICULTY`는 음절 1, 초성 2, 모음 3, 받침 4의 예상 혼동 순서를
|
||||
기록한다. 각 묶음의 세 오답은 서로 다른 규칙과 순위를 가진다. 이 순서는
|
||||
출제 메타데이터이며 청감·발음·청력에 대한 실측 난이도나 임상 척도가 아니다.
|
||||
새 단어를 넣을 때는 표제어·의미·변형 규칙을 검토하고 이 목록을 갱신한다.
|
||||
Reference in New Issue
Block a user