明るさシステム設計

概要

守備範囲: 明るさをどう持ち、行動の可否をどう決めるか。単位(EV)・行動が見る明るさ(手元・ 視界)とそれを導く環境光・各項(時刻・天気・樹冠・地面の反射・光源)がどのオブジェクトのどのプロパティ に載るか・行動ごとの要求をどこに書くかを決めます。

境界: 天気そのものの駆動は ClimateSystem.md、土地と道は ExplorationSystem.md、火の維持と燃料は FireSystem.md。 どの値をいくつにするか(照度とEVの換算・しきい値・土地ごとの樹冠と反射・その結果1日に何時間 行動できるか)は ContentSkeleton.md 8.1〜8.1.5節が持ち、本書は 繰り返しません。風雨で屋根の下でない場所での行動ができないことは明るさでは表さないので(同 8.1節)、 それを表す値(風速)としきい値も本書の外です(同 8.1.5節)。火の暖房効果も本書の外です——炉は明るさと同じ 1つの modify で気温も上げますが、 暖まることを表すのは別のプロパティ(ambient_temperature)なので、その値は FireSystem.md 9.2節が持ちます。

実現方法: どの値も普通の数値プロパティで、props の value(土地ごとの定数)・range(クランプ、 GameElementDefinition.md 6.3節)・base(同 6.5節、値の土台。本書 6.1節)・ modify(同 8.3節)・stages(同 6.4節、しきい値の境目。本書 8節)・conditions(同 14節)だけで 組み立てます。計算式を持ち込む derived(同 17節)は使いません。

在処: 定義は src/assets/world-codex/ の core.yaml(world の環境光と、場所が共通で持つ明るさ)・ locations.yaml(土地ごとの樹冠と反射、移動と探索の条件)・voyage.yaml(海区・本土・筏)・ fire.yaml(炉と松明)・characters/player_character.yaml(キャラクタの明るさ)。

確定度と実装状況: 確定しているのは5.1節(火の段取りは日が暮れるまでに済ませる)と6.1節(値の 土台を base で明示する)だけで、他の記述はすべて暫定です。 2節〜5節・8節・9節は実装済みで、未実装なのは空を継がない場所(7節) ——入れる洞窟(shallow_cave)は空を継ぐので、この形を使う場所がまだ1つもありません。使うのは奥の 洞窟で、【いつか: 洞窟内部】(Someday.md)待ちです。

1. 明るさはEV(照度の2を底とする対数)で持つ

明るさを表すプロパティはすべてEVスケール(EV = log2(照度 / 2.5 lx))で持ちます。 1段が2倍に あたるので、樹冠が通す割合も天気の透過率も地面の反射率も、掛け算ではなく足し算の項になります。

スケールを名乗るのは本節で、プロパティ名ではありません。 EV(Exposure Value)は本来カメラの露出 設定を表す量で、明るさそのものの単位ではないためです。名前が名乗るのは量のほう(brightness)で、 ambient_temperature が摂氏を名乗らないのと同じ扱いです。エンジンが持っているのは加算(modify・base)だけなので、この形でなければ 9桁に渡る照度を扱えません。値の由来と換算表は ContentSkeleton.md 8.1.1節にあります。

光源が複数あるときも単純加算です。 対数なので厳密には正しくありません(10 lx のランプ2つは 20 lx =1段の増加ですが、加算では2段になります)。この誤差は引き受けて、代わりに光源の値を「暗闇から 何段持ち上げるか」で決めます(同 8.1.1節)。しきい値との比較にしか使わない値なので、誤差が線を 跨がなければ結果は変わりません。乗算や「大きいほうを採る」といった合成をエンジンへ足すよりも、 定義ファイルが加算だけで書けることを優先します。

EVは連続量なので小数を書けます(GameElementDefinition.md 6節)が、いまの項はすべて整数です。

2. 行動が見る明るさと、それを導く環境光

行動の可否を決めるのは、次の明るさだけです。

概念 プロパティ 決めるもの
手元の明るさ hand_brightness 手元の細かい作業ができるか
視界の明るさ looking_brightness 屋外で採れるか・土地の間を移動できるか

残る ambient_brightness は、これらを導くためだけに在ります——その場を満たしている、据え付けの光源が 入っていない明るさです。据え付けの光が視界を明るくしないこと(3節)と、暗さの底(4節)が、この値の 存在理由です。

宣言は、オブジェクトの木(world → 土地 → キャラクタ → 持ち物)を下りながら育ちます。

オブジェクト プロパティ 土台(base) 自分の value 光源が届くか
world ambient_brightness — 0(時刻と天気が modify) —
場所 ambient_brightness 祖先の ambient_brightness 樹冠+地面の反射 —
場所 hand_brightness 自分の ambient_brightness 0 据え付けの光源
キャラクタ hand_brightness 場所の hand_brightness 0 持っている光源
キャラクタ looking_brightness 場所の ambient_brightness 0 持っている光源

場所は looking_brightness を持ちません。 これが「据え付けの光源は視界を明るくしない」の全部です(3節)。

場所の明るさを配るのは location trait(core.yaml)で、土地だけでなく海区・本土・筏(voyage.yaml)も 同じものを持ちます。筏が持たなければ、乗り込んだ瞬間に明かりの継ぎ目が切れます——場所を名乗る物が 間に挟まると、祖先を辿る土台はそこで止まるためです。ambient_brightness の value は trait に置かず、 場所ごとに必ず書かせます(樹冠と反射は場所ごとに違い、既定値を置くと書き忘れが「開けた土地」として通る)。

# core.yaml — 世界(樹冠も反射も無い場所の明るさ)
props:
  ambient_brightness:
    # hourのstagesが太陽高度を、weatherのstagesが透過率をmodifyする
    value: 0
    range:
      min: -6
      max: 17
# core.yaml の location trait — あらゆる場所が持つ明るさ(valueは継承先が与える)
props:
  ambient_brightness:
    base: {subject: ancestor}      # 祖先のambient_brightness(通常はworld)が土台
    range:
      min: -6
      max: 17
  hand_brightness:
    value: 0
    base: {subject: self, prop: ambient_brightness}       # クランプ済みの環境光が土台
# locations.yaml — 密林(樹冠 -7・反射 -2)
traits: [location, explorable]
props:
  ambient_brightness: {value: -9}
# characters/player_character.yaml — キャラクタ
props:
  hand_brightness:
    value: 0
    base: {subject: ancestor, prop: hand_brightness}      # 場所の手元(据え付けの光源を含む)
  looking_brightness:
    value: 0
    base: {subject: ancestor, prop: ambient_brightness}   # 場所の環境光だけ

3. 光源は、置かれた場所だけで届き先が決まる

どの光源も、条件と寄与の同じ2つの宣言を書きます。 種類による書き分けはありません。

# 松明。キャンプファイヤーも獣脂のランプも、値が違うだけで同じ2つの宣言(火の状態の持ち方はFireSystem.md)
torch:
  passives:
    - conditions: [{prop: lit, eq: 1}]
      modify:
        parent:
          hand_brightness: 11
          looking_brightness: 11

親がその値を持っていなければ届きません。

置き方 parent 手元 視界
手に持つ キャラクタ ○ ○
地面や炉に据える 場所 ○ ×(場所は looking_brightness を持たない)
袋にしまう 袋 × ×

在るのは松明と炉だけです(fire.yaml。炉は火が生きている間、火力の段によらず同じ明るさ)。 松明の灯し方は FireSystem.md 3.1節・3.1.2節、1本で何が開くかは ContentSkeleton.md 8.1.1.4節。 獣脂のランプは、獣脂そのものがまだ島にありません。

キャンプファイヤーが視界を良くしないのは、それが動かせない設置物だからです。 光源自身は「自分は 視界に効かない」と知りません。持ち歩けない明かりは道に付いてこないので、焚き火のそばから夜の道へ 出発することはできません——松明を地面へ刺した場合も同じで、刺した松明は歩き出した本人を照らしません。

同じ理由で、袋の中へしまった光が土地を照らすこともありません(袋はどちらの値も持たないため、 modify は黙って無視されます)。

4. 暗さには底がある

ambient_brightness は range で −6 を下限とします。 これより暗い場所を区別しません。

底があるのは、光源が持ち上げる段数を場所によらず一定にするためです。底が無いと、密林の夜(−15)と 浅い洞窟の夜(−12)でランプの効き方が変わり、「1本で歩けるようになる」「作業できる明るさには11本要る」 という段が作れません(ContentSkeleton.md 8.1.1節)。

−6 に現実の根拠はありません——移動のしきい値(−5)の1段下、というゲーム側の都合です。上限の +17 のほうには根拠があり、雲のまったく無い空(scorching)の正午(+16)に最も明るい地面 (砂浜 +1)を足した、太陽光だけで届く最大です。

底は光源より前に効きます。 range は同じプロパティの中では modify の後に効くので、底を持つ値 (ambient_brightness)と光源が入る値(hand_brightness・looking_brightness)を分けてあります。base が読むのは相手の 実効値なので、hand_brightness はクランプ済みの ambient_brightness を土台として受け取ります。2節で土地が 環境光と手元の明るさを分けて持つのは、この順序と、3節の据え付け光源の隔離との、独立した理由からです ——どちらか一方をやめても、もう一方が同じ分け方を要求します。

5. 行動ごとの要求は、操作の conditions に1行書く

行動によって、見る値もしきい値も違います。 満たさなければその行動はできません——遅くなるので はなく、線を引きます(DesignPrinciples.md の「悪条件は、効率を 下げるのではなく、できる/できないの線で効かせる」節)。

行動のクラス 見る値 しきい値 段で書くと 書く場所
土地の間を移動する looking_brightness −5 dim path の travel(ExplorationSystem.md 3節)
屋外で採る・探索する looking_brightness +3 bright 土地の explore と、各採取の操作
手元の細かい作業 hand_brightness +5 bright 全レシピ共通の crafting_conditions と、その作業を宣言している interactions

採る側が作る側より2段低いのは、要る明るさが違うからです。 +5(80 lx)は倉庫や廊下の明るさで、 縫製の推奨(750〜1,500 lx)には遠く及びません——それでも手元の作業を通すのは、屋内の粗い作業の下限が そのあたりだからです。屋外で草を採るのに要るのはそこまでではなく、現実の熱帯雨林の林床は快晴の正午で 600〜2,500 lx(歩けるし採れる明るさ)。+3(20 lx)は、その林床が曇りや朝夕で落ちたところに置いて あります。

条件の側には数字を書きません。 しきい値を持つのはキャラクタ側の stages だけで(8節)、 条件は段の名前で見ます。条件は行動の数だけ増えるので、境目をそこへ書き写すと、揃っているかを 見るものが1つも無くなります。

path:
  interactions:
    travel:
      conditions:
        - {in_slot: fixtures}
        - reason: too_dark
          subject: agent
          prop: looking_brightness
          in_stage_or_above: dim
        - reason: too_stormy
          not: {subject: agent, prop: wind_speed, in_stage_or_above: gale}

線はこう引いてあります。 条件が掛かるのは、レシピの工程すべて(crafting_conditions ——3つ以上の物を組み合わせる複雑な作業なので、例外を置きません)と、暗いと形を損なう手作業 (打つ・剥ぐ・撚る・縒る・編む・削る・捌く・器を割らずに掻き出す)と、目で探して選び取る屋外の行動 (探索・草を刈って採る・木に登って実や葉を採る・木を伐る・海を見張る・銛で突く・海鳥を捕る)と、 陸の道を歩くこと。 手が覚えている粗い動作(食べる・くべる・石を積む・火種を落とす・焼け石を落とす・窯や実を割る・ 穴を開けて飲む・餌を置く)と、海を渡ること(voyage.yaml。理由は Voyage.md 3.10節)には書きません。

この一覧は、風雨の線でもあります。 明るさの条件を書いた操作は、風雨の条件(too_stormy)も 書きます——嵐の日、屋根の下でなければ何もできないからで、値としきい値は ContentSkeleton.md 8.1.4節・8.1.5節が持ちます。上の「書きません」の 側も同じで、粗い動作と海を渡ることは風雨でも止まりません。

見張っているのは2つが重なっていることだけです。 tests/world-codex/stormOutdoors.test.tsが全数で 両向きに見るので、片方だけを書いた操作はそこで落ちます。一方、どちらも書き忘れた操作は拾えません ——「この操作は明るさを要求するべきか」を見るものが無いからで、そこは上の【いつか: 明るさ条件の検査】が 受け持ちます(まだ在りません)。風雨の側に独立した見張りは要りません——その検査が入れば、重なりを 通して風雨の書き忘れも一緒に落ちます。

目の前の相手を殴ることにも書きません(animals.yaml の strike)。探す作業が無く、当たり外れは 一撃の候補の重みが表しています。塞ぐと、夜に襲われた側が手を出せなくなります。怪我への手当ては そもそも書く場所がありません——操作ではなく枠へ入れることだからです(injuries.yaml の treatable)。

夜、焚き火のそばで作ることはできますが、採りには出られません。 採る側のしきい値が低くても届かない のは、作る側が hand_brightness(据え付けの光源を含む)を、採る側が looking_brightness(含まない)を 見るためです。

移動できるかは、出発する土地の明るさで決めます。 道は自分の明るさを持ちません——道の型が持って いるのは行き先の識別子だけで(ExplorationSystem.md 3節)、条件から行き先の土地のプロパティは辿れない ためです。

5.1 火の段取りは、日が暮れるまでに済ませる【確定】

火起こし具・火口・炉は、暗くなる前に用意しておかなければなりません。 どれも明るいうちにしか 揃えられないからです。用意していなければ、朝まで何も作れません。

火を熾すことだけは、暗くてもできます。 摩擦発火は手と耳の作業で、受け止める熾は自分で光るため、 暗さで難しくなりません。ここが夜の唯一の戻り道です——火起こし具・火口・炉が揃って いれば、暗がりでも火を点け直して手元を取り戻せます。

この道を閉じるのは、雨だけです。 雨の降る夜に屋根の無い場所に居れば、道具が揃っていても点け直せ ません(FireSystem.md 3.1.1節)。戻り道が消えたわけではなく、屋根の下——浅い洞窟 ——へ入るという一手が要るようになります。

一式を要求するのは、日が暮れる前に段取りを済ませることが1日の組み立ての中身だからです(ContentSkeleton.md 8.1節)。 戻り道を1つ残すのは、暗くなった瞬間に朝までの手番が全部消えるのを避けるためで、残す道が「用意して あれば通れる」ものであれば、備えなかったことの結果は残ります。 雨の夜に屋根のある土地に居るかも、 同じ「用意してあるか」の一部です。

出どころ: #656(火起こし具・火口・炉を日暮れ前に用意させる)と、同(雨の日は屋外で火が起こせない)

5.2 段取りの要求を、どの経路で効かせるか

5.1節の要求を今の文法へ写したもので、覆すのに人間の判断は要りません。

一式が暗くなる前にしか揃わないのは、火口(枯れ草)が屋外の探索でしか手に入らず、焚き火と覆い焼きの窯が レシピなので crafting_conditions(GameElementDefinition.md 13.3節)を 通るからです。戻り道のほうは、火を熾す操作にだけ明るさの条件を書かないことで開いています。

既にある炉へ石を積んで大きくすること(add_stone)は、粗い動作なので夜でもできます。新しく炉を 作ることはできないので、5.1節の要求は崩れません。

6. 値の土台は base で明示する

6.1 土台は、他のプロパティを指して書ける【確定】

土台を指せる base(GameElementDefinition.md 6.5節)で書きます。 文法そのものはあちらが持つので、本書はそれを使うだけです。

土台を指せる必要があるのは、1つの世界の値から、土地ごとに違う組み合わせ(樹冠と反射だけ/ それに据え付けの光源を足したもの)を作るためです。土台が「祖先の同名」に固定されている限り、 もう一方の組み合わせには別の継承の道が要ります。

出どころ: #656(base: {subject, prop})

6.2 本書が使う書き方と、退けた案

6.1節から導いたもので、覆すのに人間の判断は要りません。

# 祖先の同名(propを省略)
ambient_brightness: {base: {subject: ancestor}, value: -9}  # <!-- codex: locations.yaml object_defs.jungle.props.ambient_brightness.value -->
# 自分の別のプロパティ
hand_brightness: {base: {subject: self, prop: ambient_brightness}, value: 0}  # <!-- codex: core.yaml traits.location.props.hand_brightness.value -->
# 祖先の、別名のプロパティ
looking_brightness: {base: {subject: ancestor, prop: ambient_brightness}, value: 0}  # <!-- codex: characters/player_character.yaml traits.player_character.props.looking_brightness.value -->

読むのは土台の実効値(クランプ済み・modify 込み)で、自分の value はそこへ加算されます。 {subject, prop} は既にある参照の形そのもの(同 14.1節)なので、演算は1つも増えていません。

同じ組み合わせを作る道として、次の案を退けました。

7. 空を継がない場所は、光源だけで決まる

base を書かない場所は、value が底そのもの(−6)になります。 樹冠を −∞ にするのではなく、空から 光が来ないことを「土台を持たない」で表します。

deep_cave:
  props:
    ambient_brightness:
      value: -6                      # baseが無い=空から光が来ない
      range: {min: -6, max: 17}
    hand_brightness:
      value: 0
      base: {subject: self, prop: ambient_brightness}

これで昼でも真っ暗になり、探索には必ず光源が要ります。屋根を持つ建物を作ったときも同じ形になります。

この形の場所は、島にまだ1つもありません。 入れる浅い洞窟(shallow_cave)は空を継ぎ、親の土地を 土台に6段下げた差を持つだけなので、外が曇りの正午より明るければ中でも手元の作業ができます (Dwellings.md 5.1節)。真っ暗なのは浅い洞窟の奥で、そこへ続く道は 【いつか: 洞窟内部】待ちです(Someday.md)。

8. 境目を持つのは段(stages)だけ

明るさのしきい値は、どれも段の宣言にしか書きません。 読む側(5節の条件・9節の気温)は段の名前で 見るので、境目を動かすときに書き換えるのは1箇所です。行動の可否を決める明るさが条件の側で数字と 比べられていないことは、tests/world-codex/illuminationStages.test.ts が見張ります。

行動の可否を決める明るさは、キャラクタが同じ名前の段で持ちます(characters/player_character.yaml)。境目は 5節のしきい値そのもので、pitch_dark と dim の境はどちらも −5、bright の境は hand_brightness が +5、looking_brightness が +3 です。

props:
  hand_brightness:
    value: 0
    base: {subject: ancestor, prop: hand_brightness}
    stages:
      - {name: pitch_dark}
      - {name: dim, min: -5}
      - {name: bright, min: 5}

world の ambient_brightness も段を持ちます——dark(0未満)・dim(0〜+10)・bright(+11以上)。 気温への寄与(9節)がこの段に乗っています。境目は日射の強さで置きます——気温が要るのは「陽が どれだけ照っているか」で、行動のしきい値(5節)ではありません。同じ名前でも、プロパティが違えば 境目も違います(段の名前はプロパティごとの名前空間、GameElementDefinition.md 6.4節)。

段の側を細かくしても、できることは変わりません。

土地の ambient_brightness にはまだ段がありません。 水の蒸発(9節)だけがこれを読み、しきい値を 自分の conditions に数字で書いています。

9. 環境光を読んでいる場所

読み手 読む先 読み方
気温への寄与(core.yaml) world ambient_brightness の段(8節)の dark と bright が ambient_temperature を modify する
空の色(src/game/looks/skyTint.ts) world 曇りの正午(+11)を翳りも輝きも無い基準に、底(−6)から上限(+17)までを1本の値で読む
水の蒸発(liquid_containers.yaml、LiquidContainerSystem.md 6節) 土地 gte のしきい値

気温と空の色は world の側を見ます。 土地の ambient_brightness には樹冠と地面の反射が入っているので、 日射の代わりにはなりません。

水の蒸発は土地の側を見ます。 器を乾かすのはその器へ届いている光で、樹冠はそれを遮るからです ——森や密林に置いた器は上乗せの蒸発をしません。 焚き火や松明で減ることも無く、据え付けの光源が ambient_brightness へ入らないこと(3節)がそのまま効いています。しきい値の置き方と土地ごとの 効き方は LiquidContainerSystem.md 6節。

時刻の段(core.yaml の hour)は太陽高度で切ってあります。 赤道付近の島なので日の出6時・日没 18時が動かず、太陽は1時間に15°動くので、6時から18時までを正午をはさんで対称な段に刻み、その外側を 夜の段にしています。値の出どころは ContentSkeleton.md 8.1.3節の照度の表です。

夜の寄与は底(−6)そのものです。 月あかりは半月固定(同 8.1.1.3節)で、中天でも 0.025 lx= −6.6 と底(0.039 lx)を下回るため、月の高度で刻んでも結果が変わりません。天気の寄与は引く向きに しか働かないので、これで夜は天気によらず同じ暗さになります。

未決事項・今後の検討課題