カードと枠の表示

概要

カード 1 枚の見た目——枠・絵・状態バー・印・桟の文字——と、カードを収める枠(セル)の描き方を決める ドキュメントです。カードが並ぶレーンとエリアの配置は ScreenLayout.md、 カードを動かす操作は CardInteraction.md が扱います。

カードは紙 → 絵 → 枠の 3 層(1 節)で、何を出すかはすべてワールド側の宣言から導きます—— 状態バーはプロパティの gauge 宣言と capacity・fill から(8 節)、枠の色は種別の タグから(2 節)。UI 側の対応表は持ちません。UI 専用の宣言はプロパティの gauge (GameElementDefinition.md 6.8 節)と、 status タグ(StatusArea.md)だけです。

実装は src/game/ui/Card.ts(カード)と src/game/ui/CardLane.ts・laneCells.ts(枠)、色は src/game/looks/theme.ts、絵の解決は objectArt.ts・backgroundArt.ts で、実装済みです。

1. カードの枠

枠は境界線ではなく意匠です。実在のトレーディングカードは「枠が無い」か「しっかり太い枠が入る」かの どちらかで、その中間——境界を示すだけの中途半端な太さ——にはなりません。

構造は上から順に、紙 → 絵 → 枠です。枠が絵の上に乗るので、絵は縁でフェードさせず角丸で切り落とします (5 節)。

部分 幅 中身
左右と上の桟 8u 無地
タイトルの板 22u カード名(中央揃え・1 行・16u)
下の桟 可変(8u 〜) 状態バー、道の矢印、1 行の文字(16 節)

下の桟は中身で高さが変えます。 耐久度と内容量を両方持つ入れ物は 2 本ぶん、何も無いカードは左右と 同じ細さです。固定にすると、書くものが無いカードで帯が空いて間延びします。可変なので枠は 1 枚絵に できず、図形として描きます(意匠が入る段になったら 9patch へ移します。src/ui/nineSlice.ts ——Phaser の NineSlice は WebGL が無いと消えるので、自前のものを使います)。

中身の下は左右と同じ 8u を空けます。 上下へ同じ余白を取ると、中身を持つカードだけ下の枠が細くなり、 持たないカードと並んだときに枠が痩せて見えます。中身の上は枠ではなく絵との間隔なので、揃える相手が 違います。

状態バー・道の矢印・1 行の文字(16 節)は桟の中へ入れます。 絵の上に浮かせると、絵の濃淡に 埋もれるうえ、枠の意匠から浮きます。矢印は桟の中央に置きます(左下に寄せると、道であることが記号として 読めません)。積む順は「矢印 → 文字 → バー」で、文字はバーより上です——バーの本数はカードごとに 変わるので、後ろに置くと文字の出る位置が定まりません。

2. 枠の色は種別で変える

枠の色は場所・設置物・アイテム・怪我・動物・キャラクタで変えます。道が設置物の中に埋没する問題がこれで 解けます——同じレーンに並ぶ道と設置物は、行き先の絵を出すぶん見分けが付きにくく、桟の矢印 (1 節)だけでは手がかりが細すぎます。色は矢印よりずっと速く読めます。

種別は物の型が名乗るタグで決まります。 キャラクタも土地もタグを持つ物なので(character / location)、札の作り方を対象ごとに分けません——印もバーも個体の識別子も、同じ 1 つの経路 (cardLooks.contentOf)から付きます。道だけは例外で、行き先の土地の名前と絵を借ります。

現在地と道は同じ色(琥珀)です。 どちらも土地の名前と絵を出す場所の札で、色が言うのはそこまでです。 歩いて行けるかどうかは桟の矢印が言い、現在地はそもそも設置物レーンの左端にピン留めされている( ScreenLayout.md)ので、位置だけで一意に決まります。色をもう 1 つ使っても、 位置と矢印が既に答えている問いに二度目の答えを足すだけです。

動物の色に意味は持たせません。 血の赤は怪我、毛皮の茶はアイテム、緑は設置物、青はキャラクタが既に 取っているので、動物には残っている色相のうち他から最も遠いもの(紫)を当てます。ここで要るのは色の意味 ではなく、同じアイテムレーンに並んだときに一目で違うことです(動物はアイテムを兼ねるため、種別を決める 順序では動物を先に見ます、HuntingSystem.md 1.1 節)。

2.1 アイテムは用途で分ける

ゲームに出る物の大半はアイテムなので、アイテムが 1 色のままではレーンが同じ色で埋まります。そこで 食事(黄)・入れ物(青緑)・道具(灰)を分け、残りをアイテムの茶のままにします。

どれに当たるかはワールド側のタグが決める

UI 側に「この識別子は道具」という対応表は持ちません。持つと、後から足された物——アセットパックや新しい YAML——が 必ず既定の茶で出ます。付けるかどうかは、その物自身の宣言で決まります。

タグ 付ける物
food そのカード自身を口に入れると体の値が動く物。 自分の宣言する操作が、食べる本人の satiety・hydration を増やすこと。道具や工程を挟むかは問いません(青いヤシの実は刃物で穴を開けて飲むので食事です)
container 物を入れて運べる入れ物(item_container trait が付けます)
liquid_container 液体を受けるスロットを持つ容器(liquid_container trait が付けます)
tool 他の物の操作が使う側として指名し、使っても無くならない物。能力のタグ(cutting_tool・weapon)と重ねて付けます

タグの付け忘れはエラーにならず、素材と同じ枠で静かに出るだけなので、食事のタグは自動テストが 現物と突き合わせます(tests/world-codex/purposeTags.test.ts。上の条件をそのまま検査します)。

タイトルの板は枠より暗くします。 枠と同色のべたにすると単調で、半透明にすると背後の絵に負けて 名前が読めません。強調したいのは絵であって、名前は枠の一部です。この規約から外れるのは金だけで、 理由は 2.2 節に書きます。

2.2 アーティファクトは金の枠で、板が光を返す

artifact タグを持つ物(ContentSkeleton.md 6 節)は、種別を覆って 金の枠になります。 金の聖杯はアイテムのままですが、アイテムの茶で出るとレーンの中で素材に埋没します ——アーティファクトはゲームのゴールそのもの(GameEndings.md 6 節)なので、 持っていることが一目で分かる必要があります。

種別と並ぶ選択肢ではなく、種別の上に掛かります。 青写真(10 節)と同じ掛かり方で、実装でも同じ型 (CardFrameKind)の値です。両方に当たる物では青写真が勝ちます——出来上がるまでは「まだその物に なっていない」ほうが、何になるのかより先に言うべきことだからです。

金は単色では表せません。 金が金に見えるのは面が一様に光るからではなく、斜めに反射の光が走る からで、1 色で塗ると他の枠と同じ「ただ黄色い板」にしかなりません。そこでタイトルの板を「1 色」から 「左から順に、斜めの筋として塗る帯の並び」へ広げます。普通の札は帯 1 本=今までどおりの平らな板で、 金だけが多数の帯を持ちます。板の描き方は帯の並びだけで決まり、描く側は何の枠かを知りません。

3. 警戒している動物は輪郭を明滅させる

警戒(wariness)が安全域を外れている動物のカードは、紙の縁を赤く明滅させます (HuntingSystem.md 3.1 節)。放っておくと物を壊されたり持ち去られたりする ——つまり手を止めるべき——ことを、ステータスエリアではなくその札の上で言うためです。

4. カード名は 1 行に収まる長さで書く

板の高さが 22u で固定なので、名前は 1 行です。文字は 16u(板の高さの 7 割。これ以上大きくすると 字が板の縁に触ります)で、板の使える幅 172u に収まるのは全角 10 文字までです。今ある名前は 最長が「皮を剥いだヤシの実」の 9 文字で、すべて収まります。

名前を大きく出すより、絵を大きく見せるほうを採っています。肖像も同じ板に名前を出します——絵が 窓いっぱいに入るので、名前を下へ逃がして顔を空ける必要がありません。

英字は全角のおよそ半分の幅です(16u での実測で 1 文字 8.0u)。目安として「全角 10 文字=英字 20 文字」で 考えて構いませんが、字ごとの差は大きく(i l は全角の 0.19 字ぶん、W は 0.85 字ぶん)保証にはなりません。

検査は文字数ではなく幅で行います(tests/world-codex/bundledLocale.test.ts)。Node には文字の描画幅を測る 手段が無いので、全角 1.0・半角 0.5 の近似で数えます。この近似は実測より常に大きめに出るため、 通れば実物は必ず収まります。

製作中オブジェクトの名前は完成品と同じにします(variation_names の recipe)。自動で語を足すと、 元から限界に近い名前がはみ出すためです。作りかけであることは青い覆いが示す(10 節)ので、名前で 言い直す必要はありません。

5. カードの絵

カードの絵は src/assets/objects/<型の絵の名前>.png に置きます。ファイル名がそのまま絵の名前で、 コード側への登録は要りません(src/art/objectArt.ts)。

乗算で重なる絵は <絵の名前>_multiply.png に置きます。同じ名前の絵と、その下の地の上へ、乗算で 載ります。痣のような肌の変色——肌の上に在る物ではなく、肌そのものの色が変わったもの——を描くための 層で、通常の重ねだと絵の具に見えてしまいます。

接尾辞が合成方法そのものを名乗ります。 「怪我の絵は乗算」のように種別へ結び付けると、絵のファイルを 見ただけではどう載るのかが分かりません。名前が読み手(絵を描く人)への制約をそのまま伝えます。

面が破れている傷は、通常の絵で肌を敷いてから乗算を重ねます。 乗算は地を暗くすることしかできない ので、切り傷を乗算だけで描くと、毛皮の暗い所へ掛かったときに傷が見えなくなり、毛の上に線を引いた だけに見えます。切り傷の辺りでは毛が分かれて肌が覗くはずなので、乗算の層と同じアルファの肌を 通常の絵として先に敷きます。これで傷は地の明暗によらず同じに見えます。皮膚の下の変色(痣)は面を 破っていないので、乗算の層だけを持ちます。

一覧はビルド時に作ります(import.meta.glob)。実行時に総当たりで読みに行くと、絵をまだ用意していない object_def のぶんだけ 404 が出るためです。ファイル名の綴りが object_def と食い違っていると絵は黙って 使われないままになるので、実在の名前かどうかは自動テスト(tests/art/objectArt.test.ts)で検査します。

5.1 段で差し替わる絵は <絵の名前>_<接尾辞>.png

同じ個体のまま姿だけが変わる状態は、型を差し替えず絵だけを差し替えます。 気を失った動物、火のついた 炉がこれです。段(GameElementDefinition.md 6.4節の stages の art)が接尾辞を宣言し、 art_by_stage が指すプロパティの実効値が今その段にあれば、<絵の名前>_<接尾辞>.png を通常の絵の代わりに 使います(objectArt.ts の artNameFor)。

6. スタック数の丸

同種のアイテムは 1 枚のカード(スタック)にまとまります。2 個以上をまとめているカードは、右上に 丸で囲んだ数字で個数を出します(レーンでも手持ちでも同じです)。

名前にかぶってでも読める大きさを採ります(丸 42u・数字 24u)。枚数はカードを開かずに読む値で、 小さい画面ではカード名より先に要ります。名前を避ける大きさまで縮めると、実機で読めなくなります。

丸が札からはみ出す量は、カード間ギャップの半分(SIZE.gap / 2)で固定です。ここは枠の強調 (11 節)が使うのと同じ溝で、増やすと隣のカードの側へ食い込みます。丸を大きくした分は内側—— 名前の板の上——へ伸びるので、板の右端が全角 1.6 文字ぶん隠れます。

3 桁の数(石 100)は丸から左右へわずかにはみ出します。丸と数字を同じ比率で決めているので、 大きさを変えてもこの見え方は変わりません。収めるなら桁数で丸を横長にする必要がありますが、 3 桁になるのは石を大量に持ったときだけなので、形を分ける理由には足りていません。

7. 背景の絵

レーンの全面に敷く絵と、カードの地に敷く絵をまとめて背景と呼びます。置き場所は src/assets/backgrounds/ 直下のみで、名前は <持ち主の object_def の識別子>_<スロット名>_<敷く場所>.png です(src/art/backgroundArt.ts)。 敷く場所は lane(レーンの全面)か card(カードの地)で、それ以外はすべてワールド側の言葉です。

ファイル名 何を描くか
sandy_beach_fixtures_lane.png その土地の眺め
sandy_beach_items_lane.png その土地の地面
sandy_beach_fixtures_card.png その土地の眺め(カードの縦長)
medic_injuries_card.png 怪我を負った本人の肌
hand_lane.png プレイヤーの手(持ち主によらない)

どのスロットの絵かを、名前が言います。 「設置物レーンには土地の眺め」「怪我のカードには本人の肌」 といった対応を画面側が持つと、絵を 1 枚足すたびにコードを触ることになり、絵の一覧を見ても何に使われる のか分かりません。名前がスロットを名乗るので、画面側の規則は、「そのカードが今在るスロットの絵を敷く」 の 1 つだけです(cardLooks はカードの親とスロット名をそのまま渡します)。

その帰結として、傷の絵は傷だけを描けばよくなります——誰の身体に付いているかは地が言うので、絵の 枚数は「怪我の数 × 身体の数」ではなく足し算で収まります。そのぶん、絵に部位は出せません(捻挫と打撲は 絵では見分けが付かず、重さは状態バー、骨折のような特別な状態は印が言います)。

一覧はビルド時に作り、その持ち主が本当にそのスロットを持っているかを自動テスト (tests/art/backgroundArt.test.ts)で検査します。理由はカードの絵と同じです。

レーンの背景は次の規約に従います。

カードの地は、枠と絵の間に敷きます。設置物は持ち歩けるアイテムと違ってその土地から切り離せない もので、カード自体が土地の一部として見えるべきだからです。道のカードもここに含みます。

絵は ComfyUI で生成し、油絵風にぼかしてから横方向をシームレスに繋いでいます。プロンプト・ ワークフロー・後処理の設定は tools/comfyui/ に置いてあり、 レシピ 1 つで作り直せます。

8. カードの状態バー

そのカードが映しているものの状態を、枠の下の桟(1 節)の中にバーで出します。どれも「その値を 持つカードにだけ出る」ので、値を持たないカードの見た目は変わりません。実装は src/game/ui/Card.ts で、 バーの部品(ProgressBar)はステータスエリアと共通です。

バーはすべて 1 つの形(ゲージ)に揃えます。 何を映すバーであっても、持っているのは「割合」と 「両端がどう見えるか」だけで、そこから 1 つの関数(gaugeColorFor)が塗ります。カード側は どのバーが何かを知らず、渡された順に積むだけです。

何を映すか 両端
プロパティのゲージ gauge を宣言したプロパティの range に対する割合(8.1 節) 宣言が決める
材料のバー 今の工程が要求する素材と道具が、どれだけ揃っているか(10.1 節) 満ちる側が good
中身のバー 中身の量(fill)が、その容器の上限をどれだけ満たしているか(8.2 節) 色を別に持つ
容量のバー 入れ物に入っている物の volume の合計が、そのスロットの capacity をどれだけ満たしているか 空いている側が good

耐久度・炉の残り薪・残っている傷・意識・工程の進捗は、いずれも 1 行目のプロパティのゲージです。 UI 側にプロパティ名の対応表はありません。下に挙げるものだけがビルトインで、入れ物と中身の関係から 出る値——単一のプロパティではないので宣言に載せられない——を映します。

8.1 ゲージは、宣言した両端だけで塗る

どのプロパティがバーになるかも、どちらの端が良いかも、UI 側の対応表ではなくプロパティ自身の gauge 宣言(GameElementDefinition.md 6.8 節)が決めます。

durability: {gauge: {min: bad, max: good}}   # 尽きると使えない: 満タン側が良い
severity:   {gauge: {min: good, max: bad}}   # 残っている傷: 減るほど良い
finished_steps: {gauge: {min: neutral, max: neutral}}  # 工程の進捗: 良し悪しを言わない

8.2 中身のバーの色は、液体自身が color として宣言する

中身のバーだけは、良し悪しではなく中身そのものの色を映すので、両端からではなく液体自身が color(0xRRGGBB)として宣言した色で塗ります(水は青、茶は茶緑、油は黄色、 liquid_containers.yaml)。何色に見えるかはその液体の性質なので、容器側にも UI 側にも持たせません。

UI 側に「液体 → 色」の対応表を持ちません。 表を持つと、後から足された液体——アセットパックや新しい YAML——が必ず「色の分からない液体」になり、YAML を書くだけでは画面に出せなくなるためです。UI が 知っているのは「液体は color という名前で自分の色を宣言する」ということだけで、液体の種類は 一切知りません。色を宣言していない液体は灰色で出ます(中身があること自体は見えます)。宣言し忘れは 自動テスト(tests/world-codex/liquidContainersYaml.test.ts)が捕まえます。

この色にタグは要りません。 中身のバーはカードに 1 本しか無く、1 つの物が 2 つの color を持つ こともありえない(プロパティ名は物ごとに一意)ので、タグにして数を数えられるようにしても得るものが ありません。同じ理由で、このバーが読むもう一方の値——中身の volume と容器の capacity——も 名前で引いています。

空の容器にも割合 0 のバーを出します。 空であることも中身の情報で、バーが消えてしまうと 「空なのか、そもそも液体を入れられないものなのか」が見分けられなくなるためです。

9. カードの印

その札について、絵を描き替えずに言いたいことを、絵文字ひとつで絵の左下へ小さく重ねます。映して いるものの状態(血が流れている🩸・手当てが当たっている🩹)も、その札が今どう扱えるか(未解放の レシピの🔒、Windows.md 9.3 節)も、同じ印で出します。

9.0 出血の印は、負っている本人まで上がる

傷のカードは開かないと見えません。 レーンに並ぶのは動物やキャラクタ本人の1枚で、負っている傷は その中の injuries スロットにあります。傷のカードにだけ印を出すと、流し見しているあいだに失血が 進み、開いて初めて気付くことになります。

そこで、血が流れている物を中に抱えている物にも同じ印を出します。動物のカードにも、キャラクタの ポートレイトにも、同じ 🩸 が同じ意味で出ます。

9.1 動かなくなったことは、大きく出てから上部へ残る文字で言う

印で足りるのは「その物に何かが起きている」までで、動かなくなったことは絵の左下の絵文字1つでは 伝わりません。 気を失った動物なら、レーンを流し見して気付ける強さが要ります。

そこで状態の名前を、絵の上へ文字で重ねます(CardContent.overlay)。1つのオブジェクトに1つの絵 は崩しません——状態ごとに絵を差し替えると、動物の種類 × 状態の数だけ絵が要りますが、文字なら状態の 数だけ言葉を足せば済み、どの動物にも同じものが効きます。

現れ方は次のように移ります。

  1. 立った瞬間は大きく、カードの中央へ出して気付かせます
  2. 0.9秒後に縮んで、窓の上部へ収まって残ります。以後はそこが「今こうなっている」の定位置です

出っぱなしで大きいと絵を潰し、初めから小さいと気付けません。文言が変わらない差し替えでは掛け直しません ——時間経過の再生で何度も差し替わるので、そのたびに大きく出ては読めなくなります。

10. 製作中オブジェクトのカード

まだ出来上がっていないもの(RecipeSystem.md の製作中オブジェクト)は、 絵に青をかぶせ、枠も青にしたカードで出します(COLOR.cardInProgress と、枠の blueprint)。 青写真です——設計図はあるがまだ物になっていない、という状態そのものを指す図像なので、覆いの青と 枠の青が同じことを言います。

10.1 作りかけのカードが出す 2 本のバー

上に材料の充足率、下に工程の進捗を出します(8 節の桟の中)。

11. 枠(セル)は一級の単位

レーンが並べるのは枠(LaneCell)で、カードはその中身です。 位置=添字なので、枠ごとの装飾を 位置で指せます。渡し方も枠の配列 1 本で、カードの並びと枠数の 2 本立ては持ちません。

cells: readonly {
  card?: CardContent;     // 居なければ空き枠
  accepts?: CardContent;  // その枠が受け入れる物(名乗る枠なら、今出している1つ)
  borderColor?: number;   // 枠の縁を染める色
  overlay?: string;       // 「2/3」など、カードに重ねる表示
}[];

枠は背景 → カード → 重ねる物の 3 層で描きます。空き枠の破線と縁の色は 1 層目、重ねる文字は 3 層目です。

どちらもカードが入った枠で隠れてはいけない(縁を塗りにしないのはそのため)のですが、隠れない ようにする手立てがあります。縁はカードの外側を回ることで、文字はカードより手前へ置く ことで隠れません。縁が前者を選ぶのは、手前へ上げるとカードが持つスタック数の丸(6 節)を 横切ってしまうためです。数字はそのカードが何個かを表すもので、枠がどれかより先に読めるべきなので、 縁の方が下がります。

縁の太さはカードの矩形のすぐ外側の余白——カード間ギャップの半分であり、レーンの左右の余白とも 同じ——をちょうど埋める幅です。隣のカードにも掛からず、レーンの端でも切れません。

受け入れる物を名乗る空き枠は、紙の代わりにその物のカードを薄く敷きます(accepts)。 何を入れる枠なのかは名前と絵が一番早く伝えるためです。破線は薄めずに重ねます——薄めると「まだ 空いている」ことが読み取れなくなります。当てはまる型が複数あるなら、1 秒ごとに順に送ります ——どれか 1 つを選んで出すと、その型でなければ入らないように見えてしまいます。速さの理由は PlayScene の EMPTY_CELL_CYCLE_MS が持ちます。

名乗るのは、同じ並びの中で枠によって受け入れの宣言が違うときだけです(typesShownInEmptyCells)。 どの枠も同じものを受けるなら、そこへ何が入るかはその場所そのものが既に言っているので、枠が言っても 二度目の答えになります——手持ちの空き枠に持ち物の絵を順に送っても、何も絞られません。宣言が枠ごとに 違う並びの例が炉の火床で、火の中の枠と石の上の枠が別のものを受けます (FireSystem.md 1.1 節)。当てはまる型がまだ 1 つも無い枠は、敷くものが 無いので紙のままです。

これらは枠の情報であって、カードの情報ではありません。 Card は設置物・アイテム・手持ち・装備・ 怪我・レシピ一覧のすべてが通る共通のクラスなので、1 つの画面の都合で CardContent を広げると、使わない フィールドを全レーンが運ぶことになります。Card は枠のことを何も知りません。

枠の並びは slotCells(view/slotCells.ts)が作ります。縁の色と重ねる文字を持つのは材料の枠 (13 節)だけで、それ以外のレーンはスロットの宣言をそのまま形にした枠(plainCells)——カードの 入った枠を前から並べ、そのあとに末尾の受け皿(12 節)ぶんの空き枠を足したもの——が並びます。

12. 末尾の受け皿(枠が増えるレーン)

落とせば枠が増えるスロットでカードを受け入れるレーンは、並びの末尾に破線の空枠を 1 つ出します。 アイテムレーン・設置物レーンと、受け入れるスロットの子ウィンドウ(装備)が対象です。どのレーンかで 例外は作りません——出すかどうかを決めるのはスロットの宣言(枠が増えるか・受け入れるか)だけです。

枠数を宣言していないスロットは、カードを落とすたびに枠が 1 つ増えます(cell_count なし、 SlotSystem.md 3 節)。末尾の空枠はその増える先で、ここへ落とすと並びの 末尾へ入ります。1 つだけなのは、一度に増える枠が 1 つだからです。中身が空のときは 1 枚も描かれない ので、そこがドラッグ&ドロップを受け付ける場所だと分かるのもこの枠の役目です。落とし先の枠も、隙間を 示す細い縦帯ではなくこの空枠そのものを塗って示します。

枠数を宣言したスロット(手持ち・入れ物の中身)は落としても枠が増えず、空き枠が常に見えているので、 この空枠は出しません。受け入れないスロット(怪我)にも出しません——出せば「落とせる」と誤って伝える ことになるためです。

一度に見える数(4枠)を超える枠を持つスロットも、枠数のぶんだけ並べます(編み籠の中身は10枠)。 見える数はウィンドウの幅の話でしかなく、入り切らない枠は横スクロールで送れるので、「あと何種類 入るか」は覗いて分かります。頭打ちに掛かるときは次の枠の頭を覗かせて、まだ続くことを示します。

13. 製作中オブジェクトの材料の枠

製作中オブジェクトの材料スロット(RecipeSystem.md)だけは、枠の並びを 残りの要求から組み立てます(materialCells)。

14. 出来事は、起きた札の上に文字で浮かぶ

世界の形が変わらない出来事は、それが起きたオブジェクトの札の上に短い文字が浮かんで消えます (signalLabel、GameElementDefinition.md 9.8 節の signal)。 武器を重ねて外した回はカードも値も動かないため、これが無いと操作そのものが効かなかったように見えます。

15. 加熱されている札は、絵を覆って残り時間を出す

火にかかっているものは、絵の上に熾色の覆いをかけ、その上に「変わるまでの残り時間」と進み具合の バーを出します(CardContent.cooking、FireSystem.md 7 節)。焼き上がる 肉も、熱を溜めて焼け石になる石(同 9.1 節)も、同じ 1 つの覆いで出ます。

絵は隠れてかまいません。 火にかけた物は炉の中で暗く沈んで見えないもので、そこで読むべきは姿では なく数字だからです。覆いは、その数字を絵の濃淡から浮かせる下地も兼ねます。

なぜ桟のバー(8 節)ではないのか

桟のバーは常に出ます。 加熱は料理の間だけの一時の状態で、しかもほとんどの料理は一度火にかけたら 最後まで通します。常時出す枠は、腐敗のようにいつ見ても意味のある値のために空けておきます。

加熱で読みたいのは割合ではなく絶対値です。 焼き上がりを過ぎると焦げる(同 7.2 節)ので、要るのは 「あと何分か」で、バーだけでは答えられません。数字を出す場所は桟には無く、絵の上にしかありません。

どの値を見るかは、名前ではなく宣言が決める

画面はプロパティの名前を 1 つも名指ししません。 進みを受ける名前は 1 つではなく(料理は cooking_progress、石は heat_soak)、名前で選ぶと、火の中で変わる物を足すたびに画面を直すことに なります。覆いに回すかどうかは、その値の宣言だけで決まります。

2 つ以上が同時に進んでいたら、先に変わるほうを出します。 数字が答えているのは「次に何が起きるのは いつか」なので、変わってしまう前に気付ける側を出します。

出すかどうかは、場所ではなく「今進んでいるか」

UI は「炉の中にあるか」を見ません。 加熱を進めているのは炉の火力の段が宣言した寄与 (FireSystem.md 7 節)で、 その寄与が今効いているかどうかがそのまま答えになります。火から出せば寄与が外れ、火が消えても外れ、 どちらの場合も覆いは消えます。炉のスロット名も火力の段も、画面側は知りません。

残り時間は、その寄与のまま進んだときに max へ届く(on_max が起きる)までの tick 数から 出します(PropertyValue.ticksUntilMax)。火力が変われば残り時間も変わりますが、毎 tick 引き直すので勝手に追随します。

数字は「あと何 tick か」ではなく「その tick が回るまであと何分か」です(World.minutesUntilTick)。 加熱が進むのは tick が回る瞬間だけなので、焼き上がる時刻は必ず tick の格子の上にあり、そこから 今の時刻を引いたものが残り時間になります。

このため、tick の間で時間が進むと、加熱がひとつも進んでいなくても残り時間は減ります。5 分の行動を 挟めば 120 分は 115 分になります——焼き上がる時刻は動いていないのだから、これが正しい減り方です。

同じ tick に入れた物どうしは残り時間が揃います。 00 分に入れた肉と 14 分に入れた肉は、どちらも同じ 瞬間に焼き上がるので、同じ数字を出します。数字が指しているのは「この物がどれだけ焼けたか」ではなく 「いつ変わるか」です。

中で焼けているものは、抱えている札まで上がる

火にかけた物は炉を押して開くまで見えません。 そこにだけ出していると、開かないあいだに焦げます。 そこで、中で一番早く変わるものの残り時間を炉の札にも出します——出血の印が負っている本人まで 上がるのと同じ理由(9.0 節)で、辿るのは直下の子までです。

バーは良し悪しを言わない 1 色

生 → 焼きは完成へ、焼き → 焦げは劣化へ進みますが、その進み先が完成なのか焦げなのかを画面は 知り得ません(on_max が生む型が良いものかは判定できない)。片方だけ赤くする規則は書けないので、 工程の進捗(10.1 節)と同じく両端 neutral の 1 色にします。焦げが近いことは、色ではなく数字が言います。

16. 筏の札は、今の積載での推定日数を桟へ出す

積み下ろしの間、今の積載で本土まで何日かかるかを、幅のまま筏の札の桟へ 1 行の文字で出します (GameEndings.md 9.3 節)。足りるかどうかは言いません。

出す先が筏の札なのは、積み下ろしをしているのが筏だからです。積荷を並べるのは筏のスロットへの操作 なので、そのあいだ筏は現在地の札としてピン留めされています(ScreenLayout.md 7.1.1 節)。岸へ繋いである間も設置物レーンに札が出るので、出航前も航海中も同じ 1 枚が答えます。 ステータスエリア(StatusArea.md)はキャラクタ自身のプロパティを並べる場所なので、 別のオブジェクトの値も、割合でない数も入りません。

バーではなく文字にします。 読みたいのは割合ではなく「何日か」という絶対値で、しかも幅を持つ ——1 本のバーでは下限と上限を同時に言えません。数字を出す場所は絵の上(15 節)と桟しかなく、絵を 覆ってよいのは火にかかっている物のように姿を見る必要が無い場合だけなので、桟へ入れます。

数は 2 つの掛け算です(GameEndings.md 12.6 節)。

日数の幅は、そのまま海区数の幅です(GameEndings.md 12.6 節)。 積載の側に不確かさを持ち込まないため、見積もりに入れる速さは次のように決めます。

どの海区で測るかは、これから渡る海区です。 海に浮かんでいれば今いる海区、海岸に繋いであれば 出航の卓(set_sail の pick)が立てうる海区で、画面は「出航」という名前を知りません ——自分を型で名指した場所へ移す抽選が出航で、行き先が海区かどうかは行き先が横断時間を持つかで決まります。 重みが 0 の候補=面していない海区は挙がらないので、出航地点ごとの差がそのまま日数の差として読めます (GameEndings.md 5 節後段)。抽選の候補が 2 つ以上あるときは、両方を 含む幅にします。

刻みは半日で、どちらの端も切り上げます。 分まで出すと海図の粗さ(±5 海区)より細かい数字になり、 半端な半日ぶんの水と食料も要るためです。両端が揃うのは海図の幅が消えたとき(渡り終えた海区)だけで、 そのときだけ 1 つの数になります。

実装は src/game/view/voyageForecast.ts(見積もり)と src/game/looks/timeTexts.ts(文言)、 桟への配置は src/game/ui/Card.ts の CardContent.railText で、実装済みです。プロパティの名前以外は すべて宣言から引きます——段の境目も、段が引く分数も、海図の幅も、YAML に書かれた値がそのまま出ます。