どう測ったか

  • 記録DBに残った32局の盤の形と問題文を分析しました。
  • ソースのコピーを、盤がどの経路から出たかだけを記録するように直し、難易度ごとに30局ずつ作りました。
  • 実際のサービスと同じ Mac の gemma4 で、新しい盤を作る過程を難易度ごとに3回ずつ動かしました。盤の設計は時間制限を外して、さらに6回受け取ってみました。

盤が先の経路は一度も成功しませんでした

作った90局がすべて予備の経路から出ていました。1回目の記事で見た単語連結型の配置器です。真ん中に単語を一つ置き、1文字ずつかけてつなげていくので、盤が対称にならず、片側が空きやすくなります。

  • 180°対称の盤: 90局中0局
  • ふつう・むずかしいの60局中18局に、6文字以上の答えが入っていました。盤が先の経路なら5文字を超えることはありえません。
  • 平均の単語数: やさしい10.7個、ふつう14.9個、むずかしい19.9個
  • 作る時間の中央値: やさしい0.6秒、ふつう1.9秒、むずかしい6.6秒。むずかしいは10局に1局が22.9秒を超えました。

実際に保存された盤も同じです。盤が先に変えた後に保存された8局のうち、対称の盤は0個で、5局には6〜8文字の答えがありました。

これまで気づかなかったのには理由があります。盤が基準を満たすかを見る検査は、白マスの割合、ヨコ・タテの問題数、真ん中の交差の数、連結しか見ません。予備の経路の盤もこの基準は満たすので、90局中89局が「合格」でした。

あらかじめ描いておいた盤24個に、盤ごとに10回ずつ単語を埋めてみました。240回すべて失敗し、最も多く埋めた場合でも単語の枠の28%でした。平均は14%です。別のシードで盤ごとにさらに20回ずつ回しても、一度も成功しませんでした。ランダムな盤も120個中0個でした。あらかじめ描いた24個のうち2組は同じ盤でした(ふつう4・7番、むずかしい5・8番)。

なぜ埋まらないのか: ハングルの音節

英語のクロスワードはアルファベット26文字で交差します。韓国語のクロスワードは音節一つが1マスなので、文字の種類がはるかに多くなります。単語バンクに出てくる音節だけで545種類あり、そのうち178種類は一度しか出てきません。

そのため、交差で1マスが決まると、残る候補はほとんどありません。5文字の枠で1文字が決まったとき、その位置に同じ音節を持つほかの5文字の単語は平均0.52個で、70%は一つもありません。4文字も平均0.85個で、55%が0個です。

あらかじめ描いた盤は白マスが64〜75%で、白マスのほとんどがヨコとタテの単語に同時に属しています。単語をいくつか入れると、交差するマスのどれかは必ず候補が0個になります。実際に出た盤は白マスが36.4%で、ヨコ・タテが重なるマスは30〜37%程度です。

AIによる盤の設計: 0/9

新しい盤を作るたびに、盤の設計を15秒待ちます。9回中8回は15秒以内に答えが来ず、1回は答えが来ましたが使える盤がありませんでした。

時間制限を外して、さらに6回受け取ってみました。答えが来るまでに13〜52秒かかり、受け取った候補15個のうちルールを通過した盤は0個、自動補正した後も0個でした。ほとんどが上の図のように、片側の列を丸ごと黒く塗った塊でした。1回は答えが長くなって1,200トークンで切れました。今の構造では、盤の設計に使う15秒は毎回ただ捨てられています。

AIの問題: 2/9、そして問題の質

問題の作成は25秒の制限です。9回中7回が時間を超え、単語バンクの基本の文で出題されました。新しい盤一つを受け取るまでに35〜54秒、中央値44秒かかり、そのほとんどが2回のAIの待ち時間です。

実際に保存された8局も似ています。AIが問題を書き直した盤は2局だけです。

AIが書いた文にも問題がありました。

  • クイズではなく辞書のような説明になります。「2026年にアメリカ・メキシコ・カナダが共同で開いたサッカー大会」が、「多くの国のサッカー代表チームが集まって行う国際的なサッカー大会」になりました。難しくするために入れた時事問題が、平凡な意味の説明に変わってしまいます。
  • 間違った引用: 孔子の問題に「『鶴になって学び、時にこれを習う』という教え」が出てきました。論語の最初の一節は「学びて時にこれを習う」です。
  • 時制: すでに終わった2026年の冬季オリンピックを「開催される予定の冬のスポーツの祭典」と書きました。
  • 存在しない本: ダランベールを「『筆衰論』を著した人物」としました。
  • 大田広域市を「広域自治区」としました。

単語バンクと時事の単語

  • 5文字の枠を埋めるために単語を切った項目があります。'인천국제공'(仁川国際空…)、'김포국제공'(金浦国際空…)、'시외버스터'(市外バスター…)、'국회의원선'(国会議員選…)のような8個で、問題は「…の前の部分」です。サンフランシスコの橋の答えは '금문교'(金門橋)ではなく '금문'(金門)です。
  • 問題そのものが間違っている項目もあります。'냉장고문'(冷蔵庫のドア)の問題が「食器棚を開け閉めする扉」です。
  • ウィキペディアのランダムな記事から来た答え: '시발리크급구축함'(シヴァリク級フリゲート)、'란도어과'、'일리네어레코즈'(イリオネア・レコーズ)、'임혁동음이의'(イム・ヒョク 曖昧さ回避)のような単語です。'동음이의'(曖昧さ回避)は記事のタイトルに付く言葉です。問題はウィキの最初の文を70文字で切ったものなので、'이채윤가수'(イ・チェユン 歌手)の問題が「チェユン(本名: イ・スル、…)は大韓民国の歌手である」のように、答えをほとんど明かしてしまいます。保存された8局のうち4局にこうした単語が入っていました。
  • 最新のニュースは実際にはほとんど使われていません。新しい盤の API は、ニュースを見てAIが単語を選ぶ機能をオフにしたまま呼んでいます。見出しは「ネパール」「洪水」「氷河」が入っていればネパールとヒマラヤを入れる、というルール一つにしか使いません。ほかの国の洪水の記事にもネパールが入ります。時事問題のほとんどは手で入れた12個です。
  • 難易度別の語彙の等級(よく使う言葉・普通の言葉・まれな言葉)は計算する関数があるだけで、生成器では使っていません。今の難易度は、盤の大きさ、公開マス、答えの長さだけで分かれています。

続きから解くのはうまく動きます

途中保存した盤は記録から開き直して続きから解くことができ、空の盤は消されました。答えをまとめて入れるように変えた後に終えた20問の盤は、24回と27回の入力で、24分と15分かかりました。

一つは直す必要があります。ゲームルームが5秒ごとに経過時間を保存するので、何も入力していない盤をタブに開いておくだけで時間が積み上がります。空の盤一つに13時間以上が記録されていて、待合室を通らなかったので消されてもいませんでした。

直す方向

  • 盤の設計からAIを外すか、後回しにします。今は15秒を使っても得るものがありません。盤はプログラムが作り、ヨコ・タテが重なるマスを減らした疎らな盤にします。
  • 単語を盤に合わせて探します。'○스○○' のようにマスにすでに決まった文字を渡してモデルに候補の単語を出させ、辞書で確認してから入れます。あらかじめ決めた単語バンクだけでは、4・5文字が足りません。
  • 盤をあらかじめ作っておきます。盤一つに44秒ほどかかるので、難易度ごとに裏で作って溜めておき、すぐに取り出して渡せばよいのです。そうすれば時間制限も延ばせ、検品する時間もできます。
  • 問題を検品します。時事問題はクイズ形式を守らせ、日付・時制と引用をもう一度確認します。ウィキの「曖昧さ回避」の記事と長すぎる固有名詞は除き、答えの一部が問題に入っていないかも見ます。
  • 切った単語と間違った問題を整理し、語彙の等級を実際の難易度に反映します。

保存された32局すべてです。単語の数と盤の使用率ははっきり良くなりました。ただ、その盤は計画した「盤が先」ではなく予備の経路から出たもので、残る仕事は盤の形と問題の質です。

この段階の実際のコード

盤を埋める順序です。あらかじめ描いた盤 → ランダムな盤 → 単語連結型の配置器の順に試します。測定では前の二段階が一度も成功せず、いつも最後の行まで下りていきました。

lib/gridFill.tstypescript · 15行
for (const mask of shuffle(readyBoards(size, cap), rng)) {
  const filled = tryMask(mask);
  if (filled) return filled;
}
for (let attempt = 0; attempt < 8; attempt++) {
  const mask = makePattern(size, 2, cap, rng);
  if (!mask) continue;
  const filled = tryMask(mask);
  if (filled) return filled;
}
for (let attempt = 0; attempt < 5; attempt++) {
  const connected = tryBuildConnected(size, maxLen, bank, prefer, avoid, rng);
  if (connected) return connected;
}
return null;

時事の単語を集める部分です。新しい盤の API は useLlm を false で渡すので、ニュースに基づくAIの単語は空で、見出しはネパールのルールにしか使われません。

lib/dynamicBank.tstypescript · 18行
export async function loadDynamicBank(
  difficulty: DiffKey,
  avoid: Iterable<string>,
  options?: { useLlm?: boolean },
): Promise<DynamicBankResult> {
  const avoidList = [...avoid];
  const [headlines, wiki] = await Promise.all([fetchNewsHeadlines(), fetchWikiEntries(16)]);
  const llm = options?.useLlm === false ? [] : await fetchLlmEntries(difficulty, headlines, avoidList);
  const newsHints: WordEntry[] = [];
  if (headlines.some((h) => h.includes("네팔") || h.includes("홍수") || h.includes("빙하"))) {
    const nepal = asHangulEntry("네팔", "이번에 히말라야 빙하가 무너져 큰 홍수가 난 나라");
    const himal = asHangulEntry("히말라야", "올해 빙하가 붕괴해 네팔에 대홍수를 일으킨 산맥");
    if (nepal) newsHints.push(nepal);
    if (himal) newsHints.push(himal);
  }
  const entries = [...llm, ...newsHints, ...wiki];
  const source: DynamicBankResult["source"] = llm.length >= 12 ? "llm" : wiki.length ? "wiki" : headlines.length ? "news" : "bank";
  return { entries, source };

5文字に合わせるために切った単語です。

lib/words.tstypescript · 3行
{ word: "시외버스터", clue: "시외버스가 모이는 곳의 앞말" },
{ word: "인천국제공", clue: "서해 관문 국제공항의 앞말" },
{ word: "김포국제공", clue: "서울 서쪽 국제공항의 앞말" },

次の記事は「4. 全ソースコードと使用したツール」です。