プレイヤーキャラクタ

新規ゲーム作成時に選ぶ主人公。1キャラクタ=1つの object_def で、src/assets/world-codex/characters/ に 1ファイルずつ置く(共通の骨組みだけが player_character trait)。

character タグはプレイヤーが操作するキャラクタだけに付く。操作できない生物は、持ち運べるなら アイテム(item)、持ち運べないなら設置物(fixture)で表す。したがって「選べるキャラクタの一覧」は character タグを引くだけで得られる(WorldCodex.objectDefNamesWithTag)。

定義を分けたうえで齟齬を防ぐ方法

キャラクタごとに数値は違ってよいが、あるキャラクタだけ満腹度の定義が無い、最大値だけ変えて stages を直し忘れたといった食い違いは許容できない。これを防ぐのは trait ではなくテスト (tests/world-codex/charactersYaml.test.ts)である。

trait は「何を持つべきか」ではなく「省略したらこの値」しか表現できない。props のマージはフィールド 単位の上書き(RawObjectDef.resolve)なので、trait に既定値を置くと「range.max だけ上書きして stages は trait のまま」が素通りする。stages のしきい値は max から導いた値なので、この瞬間に 下記の不変条件が壊れるうえ、キャラクタのファイルを読んでも stages が見えないため気づけない。

そこでテストは character タグを持つ全 object_def を走査する。キャラクタを1つ足せば自動的に検査 対象になり、登録漏れが起きない。

契約

持たなければならないもの

内容
singleton true(同時に存在するプレイヤーキャラクタは1体)
タグ character
スロット hand(item を受け入れる枠。数は人物ごとに定義する)、equipment、injuries
プロパティ weight / travel_delay / pain / blood / warmth / chill_point / satiety / carbohydrate / protein / lipid / vitamin / loneliness / homesickness / comfort / company / happiness / pathogen / immunity / hydration / body_fat / wakefulness / stamina / load と、腕前(Skills.md 2 節)、その段が押し上げる上乗せ ignition_ease / quarry_sense / hunting_aim(同 5 節。重みの base の土台になる)(いずれも個体差を持たず player_character trait が配る)
アクション 休息(wait / rest / nap。下の休息節。睡眠は寝床が配る)と、限界(collapse / fall_asleep / despair。下の限界節)。どちらも player_character trait が配る
表示 ja.yaml の表示名、代替アイコン(characterCard.ts。絵が入るまでの繋ぎ)

status タグが付くのは pain / blood / warmth / satiety / vitamin / homesickness / happiness / pathogen / hydration / wakefulness / stamina / load で、宣言順もこの順に揃える(propertiesWithTag の戻り順がそのままステータスエリアの 並びになる、StatusArea.md)。trait 由来のものが先頭に並ぶのは、trait の props が キャラクタ自身の props より前に並ぶため(RawObjectDef.resolve)。chill_point は status を持たない ——見せるのは残っている熱だけで、境目そのものは衣服・寝床が押し下げる裏の値。loneliness と comfort と company も持たない——常時見せるのは間の homesickness だけで、どれもその詳細から辿って読む (下のホームシック節)。栄養素の在庫(carbohydrate ほか)は status を持たない——常に見せるのは腹が満ちているかどうかだけで、在庫は開いて見るもの (DigestionSystem.md 3 節)。ビタミンだけが在庫と別扱いなのは、 尽きた先の弊害を段が持つため(同 4 節)。immunity も status を持たない——常時見せるのは症状 (pathogen の段)のほうで、免疫そのものは体脂肪と同じく開いて見る値(同 6.2 節)。

気絶を決める consciousness は、pain と同じくキャラクタ間で共通の値としてここへ加わる予定である (押し下げる側は VitalsSystem.md 2 節。手番を飛ばす側は入ったが、 倒れている間の画面がまだ無いため、まだ実装しておらず上の契約にも含めない。同 7 節)。

値の刻み方(キャラクタ間で共通の規約)

数値のスケールは GameElementDefinition.md 6.0節に従い、 range.min は常に0——唯一の例外が immunity で、そこだけは押し下げが重なっても残る守りの底(20)を置く (DigestionSystem.md 6.2 節)。1 tick(15分)。

尽きると死ぬのは hydration / body_fat / blood / warmth (VitalsSystem.md 8 節・8.3 節)。 いずれも range.min(=0)へ達した時点で on_min が自分を destroy する、同じ形で書く。 死因を名乗るのはその destroy で、添えた reason(dehydrated / starved / exsanguinated / frozen)が消された側に残る(WorldObject.destroyedReason、 VitalsSystem.md 6 節)。 同じ名前の段を一番下に置くのは、尽きる前に警告を出すため。

域の区分(stages の不変条件)

GameElementDefinition.md 6.4節の alert。max が違っても プレイヤーの読み取り方が変わらないよう、しきい値は次の形で揃える。

荷重の効き方

効かせ方は 1 箇所——load の段(ContainerSystem.md 5 節)で、 閾値を別に持つ量は 1 つも足しません。 荷が何に効くかは「荷は、歩みの遅れと疲れにも効く」節が 決めます。

段 移動 歩みの遅れ stamina
light 通る 遅れない 減らない
laden 通る +15 分 -0.3/tick
heavy 通る +30 分 -1/tick
too_heavy 通れない — -2/tick

上の表の数値は目安です。 段ごとの遅れと削りをどこに置くかは、1 日にどれだけ往復するのが普通かが 決まって初めて釣り合いが見えます(未決事項は ContainerSystem.md 8 節)。動かないのは、削るのが時間であることと、 1 日ぶんと、荷を下ろした睡眠1回の回復が同じ桁であることだけです。

荷は、歩みの遅れと疲れにも効く【確定】

荷は、担げるかどうかだけでなく、歩みの遅れと疲れにも効きます。

出どころ: #656(重いほど移動が遅くなる/重いほど疲れる)

幸福度

happiness は、メンタルの不調を代表する1本です。 ストレス・退屈・里心が行き着く先を分けず、心の不調は すべてここへ集めます(DesignPrinciples.md)。分かれるのは原因の側 だけで、pain と homesickness がそれぞれ値を持ってここを削ります。

押し引きはすべて add で、modify は1本も刺しません。 刺すと実効値が実体値とずれて on_min が 書けなくなります(WorldCodex.requireRangeEventsOnUnmodifiedProperties)——尽きたことへ反応する道を add だけの値のまま開けておきます。意味の面でも、効くのは「快適な寝床で寝た今夜」であって、寝床を 所有していること自体ではありません。

現実の単位を持ちません(pain・stamina と同じ抽象の 0〜100)。値そのものは外から検算できず、 確かめられるのは大小の関係だけです。初期値は水分と同じ 75(安全域のやや下)——漂着した直後から ステータスエリアに出ていて、食べれば戻ることが最初の1食で分かります。

段 下限 域
miserable 0 危険
dejected 20 要注意
glum 50 留意
content 80 安全

致命的域は持ちません。 尽きても死なず、0 に達すると打ちひしがれて時間が過ぎ、その間に少し戻ります (下の限界節)。

上げるのは、食べた物のベース値

食べ物は eat の add に定数を1つ持ちます。量ではなく質で決まります——かさは satiety が、 身になる量は栄養素が既に持っているので、ここが数えるのは食べた喜びだけです。同じ「火の通った1食」 なら、丸焼きのネズミも焼いた肉と同じだけ戻します。

持つのは eat だけで、飲む操作は1つも持ちません——food を名乗る青い実(刃で穴を開けてその場で 水を飲む)も同じです。飲んで上がるかどうかは決めていません。

値 何を食べたか
6 火を通した食事(焼いた肉・ヤシガニ・タロイモ・ネズミ)
4 生でうまい果実(バナナ・ベリー・ヤシの果肉・青い実のゼリー)
2 生で食べられるだけの物(空心菜・海藻・海鳥の卵)
1 本来は火を通すもの(生肉)
0 炭(腹の嵩しか残っていない)

焼いた肉は生肉より多く戻します。 DigestionSystem.md 6節の菌と併せて、生で食べない理由が1本増えます。

値そのものは検算できません(幸福度に現実の単位が無いため)。動かないのは、火を通した物が最も戻す ことと、量が効かないことだけです。書き忘れると、その食べ物だけが心に何も残さない食事になるので、 eat を持つ型がすべてベース値を宣言していることを tests/world-codex/foodsYaml.test.ts が全数で 見張ります。

下げるのは、痛みとホームシックの段

pain の段が add で削ります——sore で −0.125/tick、hurting で −0.25/tick、unbearable で −0.5/tick。怪我も壊血病も脂の欠乏も痛みへ合流しているので、削る宣言を 1箇所に置くだけで、内側の不調が残らず心へ届きます。疼く程度なら食事で取り返せ、激痛のまま1日を 過ごすと最良の食事3食でも追いつきません。

内側の不調が痛みへ合流するのに対し、外から時間が掛ける圧は homesickness が受け持ちます(下の ホームシック節)。削る量は痛みと同じ目盛りで、削る側はこれで全部です。

寒さと雨はここへ引きません。 どちらも熱(warmth)が受け持っていて、対抗手段(火・屋根)もそちらに 付いています。両方へ効かせると、火を焚くことがうまい物を食べることの代わりになります。

時間そのものから生える圧は、下のホームシック節が持ちます。 寝床からの寄与は未実装で、 Bedding.md 8節がこの節を待っています。

ホームシック

サバイバルは知識と設備が積み上がるぶん、後半ほど易しくなります。 危機が最初の数日だけになって残りが 作業になるのを埋める圧を、時間の経過そのものから生やします (DesignPrinciples.md 「後半には、時間の経過そのものから生える圧を置く」)。

経過日数 ──▶ loneliness ──▶ homesickness ──▶ happiness を削る
                          ▲抑える
                       comfort(住居の設え) / company(世話をしている獣)

常時のバーに出るのは homesickness だけです。 溜める側の loneliness も、抑える側の comfort・ company も status タグを持たないので、増減の理由は「里心が募っている」の1本に集まります。 どれも、そのホームシックの詳細から辿れば読めます——影響の枠として並んでいるので、押せばその段が 出ます(下の抑えるのは、今いる場所節)。

孤独は、経過日数そのもの

loneliness は独立した値としては溜めません。base: {subject: ancestor, prop: day} で world の日数を 場所ごしに継ぐだけです(ambient_brightness・sheltered と同じ形)。時間の単調増加関数でしかないものを 別に持つと、同じ数が2箇所に立ちます。base にしてあるので、将来「他の生存者に会う」で modify して 下げる余地は追加のコストなしに残ります。

段の境目は日数で置きます——30・60・90日目で、初回の季節がちょうど各30日(calm→wet→dry、 ClimateSystem.md 2.3 節)なので雨季と乾季の入口に重なります。漂着した 最初のひと月は溜まりません——生き延びるだけで手一杯のあいだに里心は付きません。

季節そのものではなく日数で切るのは、プレイヤーが読めるのが日付のほうだからです。開始時刻は 8:00〜 12:00 から選ばれる(SaveDataManagement.md)ので、季節の切り替わりは境目の日の途中に来ます。日数で 切っておけば、その半日ぶんのずれが「どちらの季節に数えるか」の問題にならずに済みます。

段 日 homesickness を溜める速さ(/tick)
occupied 〜29 —
restless 30〜 +0.01
lonely 60〜 +0.02
forsaken 90〜 +0.04

ホームシックが幸福度を削る

homesickness は 0〜120 で、増える側が悪いので段は max からの割合で刻みます(pain・load と同じ 1/4・1/2・5/6、上の域の区分節)。削り方も pain と同じ形で、1段上がるごとに倍です。

段 下限 域 happiness(/tick)
settled 0 安全 —
wistful 30 留意 -0.125
yearning 60 要注意 -0.25
despondent 100 危険 -0.5

max が 120 なのは、表へ出る日のほうが先に決まっているからです。 この目盛りに現実の単位は無く、 決まっているのは「60日目に顕在化する」ほうで、30日目から溜まり始めた分が60日目のうちに留意域 (max の1/4)を越える max の範囲のうち、いちばん読みやすい数が 120 です(60日目に表へ出ることは tests/world-codex/homesickness.test.ts が見ます)。

何も打たなければ、60日目に留意域へ入って削られ始め、91日目に初めて打ちひしがれます(火を通した3食 を通した場合。tests/world-codex/homesickness.test.ts)。初回の雨季が明けるころに表へ出て、 1周回(ContentSkeleton.md 8.3 節)の終盤で手が止まり始める位置です。 追いつくのは最初の段まで——yearning からの削りは最良の食事でも埋まりません。

0 は終点ではありません。 幸福度が尽きると打ちひしがれ、120分が強制的に過ぎて happiness が +2.5/tick 戻り(下の限界節)、削りが続く限りそれを繰り返します。

繰り返す速さを決めるのは、削りと戻しの差——despondent の削りに何を食べたかを差し引いた正味で 持ち出せた量を割った時間が、次に打ちひしがれるまでの間隔です。強制の時間にも削りは走るので、 持ち出せる量もその正味で決まります。口にした物がそのまま手元に残る時間になります(食べたかどうかで 1日に奪われる時間が変わることは tests/world-codex/homesickness.test.ts が見ます)。

打ちひしがれている間も日が進むので、里心そのものはそこでは止まりません——止められるのは、下の設えと連れだけです。

抑えるのは、今いる場所

抑える手はどちらも場所が持つ値(location trait)です。キャラクタは base: {subject: ancestor} で継ぐだけなので、遠征に出れば、どちらも効きません。

場所は入れ子になるので、どちらも ambient_temperature と同じく囲っている場所から継ぎます (洞窟と住居は土地の fixtures に据わる場所で、筏は海へ出れば海区の fixtures に浮かぶ場所です)。継がないと、浜に建てた囲いのすぐ隣の 洞窟へ入った瞬間に慰めが消えます。継ぐのは下向きだけなので、外に立っている間、住居の中の設えは 効きません。

comfort(居心地)は、その場に据えた設えが押し上げます(家具・娯楽、furnishings.yaml。据えれば 効き続け、手入れは要りません)。住居の部品も同じ形で押しますが、そちらはまだ実装がありません (Dwellings.md 2 節)。

設えは、据えること以外に何の役にも立ちません——生きるのに要る物が居心地を兼ねると、対策が どうせ作る物になって圧が素通りします(ContentSkeleton.md 3 節の系統12)。 住居だけは例外で、雨風のために建てた家も押します——ただし部品の合計は homely 止まりに抑えて あるので(Dwellings.md 2.1 節)、snug へ届くには設えを積むことになります。

段 下限 homesickness(/tick)
bare 0 —
homely 20 -0.02
snug 50 -0.04

company(連れ)は、飼葉の残っている囲いが1つにつき 1 だけ押し上げます(farming.yaml。数えるのは 囲いの数で、頭数ではありません——囲いは中に何頭居るかを知らないためです)。囲いの中に生きた獣が 居ることが飼いならしたことそのもので、世話をやめれば慰めも切れます。

段 下限 homesickness(/tick)
alone 0 —
kept 1 -0.02

分けているのは、積み上がってよいかどうかが違うからです。 据えた設えは積むほど効いてよい ——それが家具・娯楽を作る値打ちそのものなので、comfort は段を重ねます。連れは積み上がってはいけません ——囲いは同じ土地へいくつでも据えられる(土地の fixtures に枠数の上限はありません)ので、頭数や囲いの 数で慰めが増える形にすると、囲いを3つ並べるだけで最後の段まで打ち消せます。company の段が1つ だけなのはそのためで、頭数でも囲いの数でも効き目は増えません。

囲い1つでも、homely まで積んだ設えでも、60日目からの増えはちょうど止まります。 90日目からの増えを止めるには snug まで積むか、homely(家でも設えでも)と家畜の 両方が要るので、家畜だけでは最後まで持ちません。逆に、家を建てただけでも homely 止まりです——「どうせ建てる家」を 1つ置けば済む圧にはしていません。

90日目からを止める snug へは、設えを積んで届きます。 単体で届く物は1つも無いので必ず積むことに なり、何をいくつ並べるかは自由で、要るのは合計の手間のほうです(点数を掛かる手間に比例させてある ので点あたりの手間はおおむね揃いますが、払う総手間は並べ方で上へ振れます——点あたりの手間の差と、 線を越える手前で行き過ぎる量の差が、どちらも乗るためです。いちばん安い積み方は stats/terrain.yaml の work_piles に出ます)。 刃物1つで届く道もあるので、斧も狩りも通らずに snug まで積めます——里心の対策は、ほかの系統の 進み具合に縛られません。

どちらもプロパティのタブには出ません(タグを1つも持たないため、Windows.md 6 節)。 読む場所はホームシックの詳細で、そこに並ぶ影響の枠を押すと、今どの段に居るかが名札で出ます (同 8.1 節・8.2 節)。出すのは段の名前だけで、点数は出しません——要るのは「今どれに届いているか」で、 あと何点かではないためです(腕前と同じ、Skills.md 5.1 節)。

専用の場所を作らないのは、押し上げているのが場所の側だからです。 周りの事情を身の値と同じ並びへ 混ぜると、homesickness が動いた理由を確かめる先が割れます (DesignPrinciples.md「周りの事情と、身に起きていることは、別の 場所に出す」)。結果から原因へ降りる道だけを開けておけば、問いを持った人にだけ答えが出ます。 気にし続けたい人は、その詳細から固定表示にすれば常時のバーへ出せます (StatusArea.md 3 節)。

遠征は心を削るものとして割り切ります。 持ち運べる慰めの席は開けません(開ければ、拠点を離れない選択と 離れる選択の差が消えます)。設えのある場所へ帰れば homesickness は減るので、遠出で増やしたぶんは 帰ってから戻す、が押し引きの形になります。

長い遠征ほど、帰るまでが遠くなります。 里心が深いまま出ると、道中で打ちひしがれて強制の時間が過ぎ (下の限界節)、そのぶんも日は進みます——遠征に出た日数がそのまま余計に溜まる形です。 拠点へ帰るまでは止める手が1つも無いので、帰りの余裕をどれだけ残すかが終盤の遠出の判断になります。

休息

時間を進める操作は、キャラクタ自身のアクションです。 どれも同じ形(duration と passives)で player_character trait に並び、ActionSystem.md 2 節の実行パイプラインを そのまま通ります。違うのは長さと回復量だけ——このうち wakefulness も戻すのは眠るものです。 時間を進める専用の仕組みは持ちません——プレイヤーが押せない限界の操作も同じ形です。

睡眠(sleep)はここにありません。配るのは寝床のほうです(Bedding.md 4 節)。 まったく同じ形の操作を寝床が自分で持ち、その回復量は寝床の段で変わります。地面の上で取れるのは 仮眠までなので、下の表はそのまま「寝床の無いところで取れるもの」の一覧でもあります。

戻すのは tick 毎です(passives、GameElementDefinition.md 11.7 節)。経過中の減り(荷・痛み・里心)と同じ足し算に入るので、受け取る量は「宣言 − その間の減り」 になります。休んでいる間も荷は担いだままで、その重さのぶんだけ戻りが薄くなります。

入口は日時のフリップカードで、押すとキャラクタ自身の子ウィンドウが開きます (Windows.md 4 節)。

長さ stamina wakefulness
wait(待機) 15分 +0.75/tick —
rest(休憩) 60分 +1/tick —
nap(仮眠) 180分 +1.25/tick +3/tick

限界

尽きた値は、強制的に時間を進めます。 0 に達すると何も手につかなくなり、一定の時間が過ぎ、その間に 少し戻ります。対象は stamina(体力)・wakefulness(覚醒度)・happiness(幸福度)で、 尽きても死にません——死ぬのは hydration / body_fat / blood / warmth だけ (VitalsSystem.md 8 節)で、これらは動きを止めるだけです。

どれも同じ1つの形で書きます。 押す機会の無い操作(trigger: tick、 GameElementDefinition.md 11.1 節)が、下限に居ることを conditions で見て、duration ぶん時間を進め、passives が tick 毎に戻す——上の休息と 同じ形で、player_character trait が並べて配ります。違うのは見る値・長さ・戻る量だけです。

手番 見る値 長さ 戻るもの
collapse(倒れ込む) stamina 120分 stamina +1/tick
fall_asleep(眠り込む) wakefulness 360分 wakefulness +3/tick・stamina +1/tick
despair(打ちひしがれる) happiness 120分 happiness +2.5/tick

起きるのは操作の切れ目で、進行中の操作は中断しません——歩き終わってから倒れます (GameElementDefinition.md 11.5 節)。

見るのは「下限に居る間」で、達した瞬間ではありません。 一度で限界を抜けられなければ、次に時間が 動いたときにまた起きます。ただし限界の手番が起きている最中に尽きた値は、同じ値でも別の値でも、その 切れ目では続きません(倒れ込んでいる最中に眠気が尽きても、眠り込むのは次に時間が動いたときです) ——戻り切らない値があると、手が戻らないまま時間だけが進み続けることになるためです。同時に尽きていた値は、 どれもその切れ目で順に引き直されます——先に起きた手番の間に戻り切っていれば、そちらは起きません。

どれも、強制の時間が過ぎる前に自分から告げます(announce、 GameElementDefinition.md 11.6 節。何を告げるかは player_character.yaml の各手番が持ちます)。1時間の道を選んで押したプレイヤーには、ボタンが言った 60 分ではなく 180 分が過ぎたことしか見えないので、告げなければ時計が飛んだ理由が画面のどこにも 出ません。告げるのを結果の側(signal)に置けないのはここで、眠り込みの 360 分では理由を聞くのが 6 時間後になります。出す場所と見え方は札の上の文字で、他の出来事と同じです (../ui/CardView.md 14 節)。

選択とセーブ

SaveData.characterId は選ばれた object_def の識別子そのもの。読み込み時に未知の識別子だった場合 (識別子の改名・旧セーブ)は先頭のキャラクタで代替して開ける(resolveCharacterDefName)。

キャラクタの絵は src/assets/objects/<識別子>.png に置けば、コード側の登録なしにカードとポートレイトへ 出る(objectArt.ts)。用意されるまでは代替の絵文字を出す。