# The Tree 詳細システム仕様書 - 文書ID: SDS-CORE-001 - 版: 0.1.1 - 状態: 実装設計基準案 - 対応ゲーム仕様: GDD-CORE-001 v0.1.1 - 対象: Godot 4.7系/GDScript - 最終更新日: 2026-08-27 ## 0. 文書の目的 GAME_SPECIFICATION.mdが「何を遊ばせるか」を定義するのに対し、本書は「内部でどの順に、どのデータを、どの状態へ変化させるか」を定義する。 優先順位: 1. GAME_SPECIFICATION.md 2. 本書 3. DESIGN_DECISIONS.md 4. ARCHITECTURE.md 5. 実装とデータ 本書内の調整値はResourceへ外出しする。状態遷移、不変条件、処理順はセーブ互換性に関わる固定仕様として扱う。 --- # 1. システム構成 ## 1.1 実行時サービス | サービス | 責務 | 他サービスから直接変更可能か | | --- | --- | --- | | SimulationKernel | 時間、処理順、コマンド実行 | 不可 | | WorldService | セル、占有、支持、掘削 | Command経由のみ | | PathService | 経路グラフ、探索、到達性 | 読み取り中心 | | ConstructionService | 建設、解体、修理ジョブ | Command経由のみ | | FacilityService | 稼働状態、予約、行列、サービス | Kernelからのみ | | InventoryService | 在庫、予約、搬送要求 | Transaction経由のみ | | GuestService | 客生成、欲求、意思決定、状態 | Kernelからのみ | | StaffService | シフト、勤務、タスク、疲労 | Kernelからのみ | | RelationshipService | 関係値、盾、段階イベント | Event経由のみ | | EconomyService | Gold、台帳、日次決算、未払金 | Transaction経由のみ | | AuthorityService | Heat、監査、証拠、処罰 | Event経由のみ | | EventService | 条件評価、イベント排他、選択肢 | Kernelからのみ | | SaveService | スナップショット、移行、整合性 | セーブ境界のみ | | NotificationService | 通知集約、ログ、場所リンク | 状態変更不可 | UIノードはサービスの状態を読み、Commandを発行する。UIから配列やDictionaryを直接書き換えない。 ## 1.2 静的データと実行時データ ### 静的データ 施設定義、客アーキタイプ、職業、種族、特性、アイテム、研究、イベント、キャラクター定義。ゲーム中に値を変更しない。 ### 実行時データ 建設済み施設、生成済みユニット、在庫、関係値、台帳、イベント状態。セーブ対象。 静的定義は一意IDで参照し、実行時データにResourceパスやノード参照を永続化しない。 ## 1.3 ID規則 | 種類 | 形式 | 例 | | --- | --- | --- | | 施設定義 | facility.カテゴリ.名称 | facility.food.cafeteria | | 施設個体 | fac-連番 | fac-00001234 | | ユニット | unit-連番 | unit-00004567 | | アイテム | item.カテゴリ.名称 | item.food.monster_meat | | イベント | event.章.名称 | event.ch01.first_inspection | | 特性 | trait.名称 | trait.night_owl | | 研究 | research.分野.名称 | research.logistics.auto_order | 表示名を変更してもIDは変更しない。連番はセーブ内で単調増加し、削除個体のIDを再利用しない。 --- # 2. シミュレーション核 ## 2.1 時刻表現 - absolute_minute: int64。Day 1 00:00を0とする。 - ゲーム開始: absolute_minute 360、Day 1 06:00。 - day = floor(absolute_minute ÷ 1440) + 1。 - minute_of_day = absolute_minute mod 1440。 - 表示時刻だけを時・分へ変換する。 - 日付、シフト、期限の比較に浮動小数点を使用しない。 ## 2.2 実時間変換 x1ではゲーム内1分を現実0.5秒で進める。 - x1: 2ゲーム分/現実秒 - x2: 4ゲーム分/現実秒 - x4: 8ゲーム分/現実秒 deltaをaccumulatorへ加算し、0.5/speed秒ごとに1分Tickを実行する。 ## 2.3 Tick制御 - 1フレームで処理するTickは最大8。 - 未処理Tickは次フレームへ持ち越し、飛ばさない。 - backlogが120ゲーム分を超えた場合は自動一時停止し、性能警告を表示する。 - 一時停止中はaccumulatorを増やさない。 - ロード直後の最初のフレームではTickを進めない。 - オフライン中の時間経過は行わない。 - 1フレーム内の途中TickがPauseを要求した時点で、同フレームに積まれた残りTickを実行しない。 - 未解決の日次境界、外部rollback隔離、破綻などのhard stopは速度変更や手動stepで迂回できず、Tick開始時刻を維持する。 ## 2.4 1分Tickの処理順 1. 実行時刻に達したCommandを確定する。 2. 時刻境界処理を行う。 3. Mana生成、消費、給電停止を確定する。 4. 建設、修理、掘削の進捗を更新する。 5. 物流の受け渡しと在庫予約を確定する。 6. 完了した施設サービスを確定する。 7. Health、Stamina、Stress、欲求、環境値を更新する。 8. 事故、関係、監査、物語イベントを判定する。 9. 施設行列を繰り上げる。 10. 対象ユニットの意思決定を行う。 11. 集約値と通知用Snapshotを更新する。 同じ段階内では、原則としてinstance_idの昇順で処理する。 通常進行TickのCommand段階はClock加算より先に実行するが、そのCommandとCommand起因のLedger、Service、DomainEventには到達予定の`absolute_minute + 1`をeffective minuteとして一貫して渡す。一時停止中に即時確定するUI Commandだけは現在時刻を使う。06:00予定Commandを終了済みの05:59以前の営業期間へ混入させない。 時刻境界で外部Domain処理が失敗した場合は、Clockをその境界分に固定し、後続のMana~Snapshot段階を保留する。確認・再試行が成功したときだけ、保留した同一分の後続段階を各1回実行する。失敗前のCommand通知を取り消したり同じCommandを再実行したりしない。 ## 2.5 時刻境界 | 時刻 | 処理 | | --- | --- | | 00:00 | 暦日の日付更新 | | 05:00 | 昼番の出勤準備 | | 06:00 | 前営業日のService確定、日次決算、昼番開始、個人AP回復、監査抽選、朝イベント、オートセーブ予約 | | 17:00 | 夜番の出勤準備 | | 18:00 | 夜番開始 | | 各時00分 | Mana集計、流行、定期効果 | 06:00境界は、保留Service確定、Ledger集計・照合、未払/破綻判定、昼番開始、AP回復、監査抽選、朝Event、Autosave予約の順。途中で外部Domain処理が失敗した場合は同じ06:00に固定し、完了済み段階を再実行しない。 ## 2.6 決定論的乱数 乱数は用途別Streamに分ける。 - world - guest_spawn - guest_decision - staff - relationship - authority - event - loot 乱数入力はworld_seed、stream_id、entity_id、local_counterから生成する。Godotの汎用hash関数をセーブ互換性へ使用せず、固定アルゴリズムで64bit値を生成する。 各Streamはlocal_counterを保存する。UI表示、アニメーション、パーティクルはゲーム判定用Streamを消費しない。 ## 2.7 Snapshot UIは毎フレームの生データではなく、Tick完了後の読み取り専用Snapshotを参照する。 Snapshotに含める: - 時刻と速度 - 資源と全体指標 - 表示範囲内のユニット位置と状態 - 施設状態、行列、予測値 - 通知差分 Snapshot生成中にゲーム状態を変更しない。 --- # 3. CommandとTransaction ## 3.1 Command プレイヤー操作は次の共通形式で発行する。 | フィールド | 型 | 内容 | | --- | --- | --- | | command_id | int64 | セーブ内単調増加 | | issued_at | int64 | 発行absolute_minute | | execute_at | int64 | 実行予定時刻 | | sequence | int64 | 同時刻の順番 | | type | StringName | コマンド種別 | | source | StringName | UI、イベント、デバッグ | | payload | Dictionary | IDと値だけを持つ | ## 3.2 Command適用 - 一時停止中のCommandはexecute_atを現在時刻とし、再開前でも確定可能。 - Commandは発行時の事前検証と、実行時の最終検証を行う。 - 事前検証成功後に状況が変わった場合、実行時に失敗できる。 - 失敗時は状態を一切変更せず、CommandResultを返す。 - 同時刻Commandはsequence昇順。 - Undoは建設予約、シフト編集、未実行ポリシー変更だけに提供する。 - 受理済みCommandは処理完了後も`command_id`と`idempotency_key`の対応を保存し、pending/in-flight/processedの全既知IDと1対1で照合する。処理済み結果だけを残して元の冪等Keyを欠落させてはならない。 ## 3.3 標準Command - BuildFacility - CancelConstruction - DemolishFacility - ExcavateCells - RepairFacility - SetFacilityPolicy - OpenFacility - CloseFacility - AssignStaff - RemoveStaff - SetShiftTemplate - SetOwnerPresence - QueuePersonalAction - StartResearch - StartInvestment - StartQuest - ChooseEventOption - ScheduleInspectionResponse ## 3.4 CommandResult | 値 | 意味 | | --- | --- | | OK | 成功 | | INVALID_PAYLOAD | データ不足 | | NOT_FOUND | 対象IDなし | | NOT_UNLOCKED | 未解禁 | | INSUFFICIENT_RESOURCE | 資源不足 | | INVALID_LOCATION | 場所不正 | | OCCUPIED | セル占有 | | NO_ACCESS | 接続不能 | | CONFLICT | シフトや予約競合 | | LOCKED_BY_EVENT | イベント中 | | CONTENT_DISABLED | 設定で無効 | | STATE_CHANGED | 発行後に前提が変化 | UIへはコード、対象、短い説明、解決候補を返す。 ## 3.5 Transaction 複数状態を同時に変更する処理はTransactionとして実行する。 例: 食事完了 1. 予約食材と料理ポイントを確認。 2. 客のGoldを確認。 3. 在庫を消費。 4. 客のGoldを減らす。 5. 宿のGoldを増やす。 6. 台帳へ記録。 7. 欲求と満足を変更。 8. 全て成功した場合だけCommit。 途中失敗時は全変更を破棄する。Gold、在庫、関係値を部分適用しない。 --- # 4. Worldデータ ## 4.1 MapBounds 論理座標はXが左右、Yが上下。Y正方向が地上。 | Rank | X範囲 | 地上Y | 地下Y | | ---: | --- | --- | --- | | 1 | -12~12 | 0~6 | 使用不可 | | 2 | -18~18 | 0~10 | -1~-4 | | 3 | -24~24 | 0~16 | -1~-8 | | 4 | -32~32 | 0~24 | -1~-14 | | 5 | -40~40 | 0~32 | -1~-20 | | 6 | -48~48 | 0~42 | -1~-28 | | 7 | -60~60 | 0~56 | -1~-40 | 範囲拡張で既存座標を移動しない。 ### 4.1.1 世界樹Rankの昇格境界 Rankは1段階ずつだけ上げ、低下と飛び級を許可しない。現在Faithを研究で消費しても進行が巻き戻らないよう、Rank条件は`faith_lifetime`を参照する。`guest_lifetime`も退店や個体破棄では減らさない。 | 対象Rank | 機械条件 | |---:|---| | 2 | Reputation 25以上、現在資産10,000G以上 | | 3 | 累計Faith 100以上、累計客300以上 | | 4 | Reputation 45以上、現在資産50,000G以上、地上・地下に稼働施設各1以上 | | 5 | 累計Faith 500以上、重要Quest完了、地上・地下に稼働施設各1以上 | | 6 | ReputationまたはUnderground Influenceの一方が65以上、地上・地下に稼働施設各1以上 | | 7 | 累計Faith 1,500以上、Reputation 60以上、Underground Influence 40以上、主要章完了、地上・地下に稼働施設各1以上 | 昇格Commandは、条件値と不足理由を全件返してから次の順で原子的に実行する。 1. 現在Rank、GameState、World、Pathを退避する。 2. 対象RankのMapBoundsへWorldを非破壊拡張する。 3. 新規境界Cellを初期化し、Path Graphを再構築する。 4. GameStateとProgressionのRankを記録する。 5. 全て成功した場合だけ`WorldTreeRankAdvanced`を発行する。 2~4のいずれかが失敗した場合は退避状態へ戻す。Rollback自体が失敗した場合は昇格境界を隔離し、再送による二重拡張を止める。同じ`request_id`と対象Rankの再送は記録済み結果を返し、同じIDを別Rankへ使った場合は競合として拒否する。 ## 4.2 Cell 各X,YはSpatialCellを持ち、その中にFRONTとBACKのLayerCellを持つ。 SpatialCell: - x、y - zone - terrain_id - hardness 0~3 - excavated - structural_anchor - mana_node - reserved_by_job LayerCell: - facility_instance_id - corridor_id - decoration_ids - utility_flags - cleanliness 0~100 - beauty 0~100 - noise 0~100 - security 0~100 - concealment 0~100 - temporary_hazard_ids 存在しないセルを暗黙生成しない。MapBounds内だけ生成する。 ## 4.3 Zone判定 - y > 0: ABOVE_GROUND - y = 0: BOUNDARY - y < 0: UNDERGROUND 施設定義zoneがANYの場合は全Zoneへ配置できる。FRONT、BACKはZoneとは独立。 複数Cell施設のZone判定は次で固定する。 - ABOVE_GROUND/UNDERGROUNDは占有する全Cellが指定Zone内でなければならない。 - BOUNDARYは配置anchorが`y = 0`であり、残りのFootprintが地下へはみ出さないことを要求する。これにより、境界を入口とする受付などは地上側へ広がれる。 - ANYはZoneを制限しない。ただしMapBounds、Layer、占有、地下Cellの掘削条件は別に全Cellへ適用する。 ## 4.4 占有 - 一つのLayerCellへ主要施設は一つ。 - 通路と主要施設が共存できるかはfacility.pass_throughで決める。 - 壁内設備はutility_flagsへ格納し、施設と共存できる。 - 建設中セルはjobが予約し、別建設を拒否する。 - 一つの施設は全占有セルをinstance側にも保存する。 Cell側とinstance側の占有情報が一致しない場合はセーブ破損として扱い、自動修復ログを残す。 ## 4.5 支持 地上建築は離散的な支持Graphで検証する。 - 世界樹幹と天然枝は無限支持Anchor。 - 人工床は1セル当たり支持容量2。 - 強化床は容量5。 - 支柱は接続Componentへ容量10を追加。 - 小型施設は1セル当たり重量1。 - 中型施設は重量2。 - 大型・重量施設は重量3。 接続Componentの合計支持容量が合計重量以上でなければ建設不可。MVPでは稼働中の建物が突然崩壊せず、損傷で支持不足になった場合は該当施設をBROKENにして退避させる。 ## 4.6 掘削 地下のSpatialCellを使用する前にexcavatedをtrueへする。 掘削基準: | Hardness | 費用 | 時間 | | ---: | ---: | ---: | | 0 | 20G/1M | 30分 | | 1 | 50G/1M | 60分 | | 2 | 100G/2M | 120分 | | 3 | 200G/4M | 240分 | - 掘削済みセルに隣接するセルだけ掘削できる。 - 地下入口からの接続がない孤立掘削は不可。 - 掘削すると同じX,YのFRONTとBACKが利用可能になる。 - Hardness 2以上の空間を幅4セル以上連続させる場合、支保設備を要求する。 - 掘削中断時は進捗を保持する。 ## 4.7 環境値更新 環境Sourceは中心値、半径、距離減衰、遮蔽タグを持つ。 更新手順: 1. 変更Source周辺だけDirty領域に登録。 2. 基礎値50から加算・減算。 3. 壁、扉、防音、Layer境界の減衰を適用。 4. 0~100へclamp。 5. 変更量1未満は通知しない。 全マップを毎分再計算しない。建設、解体、汚れ、イベントの影響範囲だけ更新する。 --- # 5. 建設システム ## 5.1 PlacementReport 建設プレビューは次を返す。 - can_build - error_codes - occupied_cells - unsupported_cells - access_points - mana_connection - total_cost - build_time - predicted_environment_delta - predicted_operating_cost - predicted_heat エラーは複数同時表示する。 ## 5.2 BuildFacility 1. 定義と解禁を確認。 2. 回転と原点から占有セルを算出。 3. Zone、Layer、掘削、占有、支持を検証。 4. 通路・Manaの接続可能性を検証。 5. GoldとMaterialsの25%をDepositとして支払う。 6. セルをjob_idで予約。 7. ConstructionJobをPLANNEDで生成。 8. 建築担当が到着すると残額を支払いCONSTRUCTINGへ。 9. 進捗100%でFacilityInstanceを生成。 10. 占有、PathGraph、環境Dirty領域を更新。 残額不足の場合はPLANNEDのまま停止し、1日経過で警告する。 ## 5.3 ConstructionJob - job_id - definition_id - origin、rotation、layer - reserved_cells - progress 0~10000 - state - assigned_builder_ids - deposit_paid - remaining_cost - created_at - started_at 進捗は整数basis pointで保存する。 ProgressPerMinute = 基礎建築速度 × 作業員Operation倍率 × 難易度倍率 × 到達性倍率 ## 5.4 キャンセル - PLANNED: Depositを全額返還、予約解除。 - CONSTRUCTING 25%未満: 未消費Materialsの75%返還。 - 25~74%: 未消費Materialsの50%返還。 - 75%以上: Materialsの25%返還。 - Goldの残額は返還しない。 ## 5.5 解体 - 営業中、利用中、在庫ありでは即時解体不可。 - CloseAndDemolishは新規受付を停止し、利用終了、在庫搬出後に開始。 - EmergencyDemolishは客と在庫を安全地点へ移し、Reputationと損失ペナルティを受ける。 - 解体完了で建設Materialsの25%を返す。 - PathGraphを更新し、到達不能になった施設をNO_ACCESSへする。 ## 5.6 施設移動 完成施設の直接ドラッグ移動は実装しない。BlueprintCopyで設定をコピーし、解体と再建築を行う。 --- # 6. Pathと交通 ## 6.1 Graph FRONTとBACKに別の歩行Graphを持つ。従業員ドア、業務窓口、昇降機がGraph間または階間Edgeを作る。 完成した通路設備は主要施設占有を歩行可能とみなすのではなく、同一Cellの`ROUTE`副占有からPath Graphへ投影する。客用通路はFRONT、業務用通路はBACKに限定する。従業員ドアはBACK側の設置Cellと同座標のFRONT Cellを、`staff`利用権限付き双方向Connector、Cost 120で接続する。投影・撤去はROUTE、Path、Connector、投影履歴を一つの原子的境界として更新する。 昇降機は運行Connectorだけでなく、同階の通常隣接Edgeを通行不能にできる垂直Topologyを先に要求する。このTopologyがない段階では、待ち時間や停止状態を通常Edgeで迂回させないため、投影要求を無変更で拒否する。 Node: - cell_key - allowed_unit_types - base_capacity - current_density - temporary_block Edge: - from、to - movement_type - base_cost - allowed_tags - dynamic_cost ## 6.2 A* コストは整数Cost Unitで計算する。 | 要素 | Cost | | --- | ---: | | 横1セル | 100 | | 従業員ドア | 120 | | 階段1階 | 150 | | 清潔ペナルティ | (100 - cleanliness) ÷ 2 | | 混雑 | max(0, density - capacity) × 75 | | 危険 | max(0, 50 - security) | | 禁止に近い忌避 | 0~10,000 | 昇降機Edgeは予測待ち時間と移動時間をCostへ変換する。1ゲーム分をCost 100として扱う。 Heuristicはマンハッタン距離×100。動的コストを含めず、過大評価しない。 ## 6.3 経路キャッシュ 次の組み合わせで短期Cacheする。 - start_region - destination_access_id - unit_access_profile - graph_revision 建設、解体、扉状態変更でgraph_revisionを増やす。混雑だけでは全Cacheを無効化せず、移動中の再評価条件に使う。 ## 6.4 再探索条件 - 次Edgeが閉鎖 - 予測到着が初期予測の2倍超 - 60ゲーム分進行なし - 目的施設が停止 - より近い同カテゴリ施設の待ち時間が30分以上短い 再探索は同一個体につき15ゲーム分に一度まで。連続再探索による振動を防ぐ。 ## 6.5 混雑表現 通路セルに硬い一人制限を設けない。複数ユニットを許容し、密度超過で速度を落とすSoft Collisionを採用する。 SpeedMultiplier = clamp(0.25, 1.0, capacity ÷ max(capacity, density)) ユニット表示は局所的にずらし、完全重なりを避ける。経路判定は中心セルで行う。 ## 6.6 昇降機 CabinState: - IDLE - MOVING_UP - MOVING_DOWN - DOOR_OPEN - OUTAGE Request: - request_id - unit_id - floor_from - floor_to - direction - priority - enqueued_at 制御: 1. 現在方向の進行先に要求がある限り方向を維持。 2. 進行先要求がなく、反対方向に要求があれば反転。 3. 同一階はpriority、enqueued_at、unit_idの順。 4. 救護、警備緊急出動、主人公、VIP、通常の順でpriorityを付ける。 5. 通常客を60ゲーム分以上待たせる場合、VIPより先に乗せて飢餓を防ぐ。 扉開閉2分、乗降は一人1分、階移動は施設定義値。容量超過は乗車させない。 ## 6.7 到達不能 経路が見つからない場合: 1. 目的予約を解除。 2. 代替施設を検索。 3. 安全な待機地点または出口を検索。 4. すべて失敗した場合、直近の有効通路へ復旧移動。 5. Warningログへ元座標、目的、graph_revisionを記録。 復旧移動はプレイヤー利益を発生させず、関係イベントも進めない。 --- # 7. Facility内部 ## 7.1 FacilityDefinition 詳細なフィールド型、Schema version、Definition/Instance/日次履歴の所有境界は`FACILITY_DATA_DESIGN.md`を正本とする。既存定義はSchema version 1で互換読込し、version 2はAccess、営業時間、Upgrade、Policy、成人向け安全境界に加えて、サービス種別ごとの時間・品質・価格・処理単位・欲求効果を必須検証する。 必須フィールド: - id、display_name、category - allowed_zones、layer、footprint、rotations - weight_per_cell、pass_through - build_gold、build_materials、build_minutes - maintenance_gold、mana_draw、power_priority - staff_min、staff_max、required_roles、primary_skill - capacity、queue_capacity - service_types、default_service_type、service_profiles - base_service_minutes、base_quality、base_price - inventory_inputs、inventory_outputs - access_point_offsets、staff_door_offsets - environment_sources - heat_per_service - policy_schema - unlock_rank、research_requirements - adult_content、content_replacement_id Definition読込時にvalidateを行い、必須値不足はプロジェクト起動を失敗させる。 ## 7.2 FacilityInstance - instance_id、definition_id - origin、rotation、occupied_cells - level - state、state_reasons - durability - local_inventory_id - assigned_staff_by_shift - active_staff_ids - queue_entries - active_services - policy_current - policy_pending - statistics_today - lifetime_statistics - created_at ## 7.3 状態評価 優先順位: 1. BROKEN 2. CONSTRUCTING 3. CLOSED 4. NO_ACCESS 5. NO_POWER 6. NO_STAFF 7. DEGRADED 8. ACTIVE 内部では複数reasonを保持する。主状態が変化したときだけ通知し、同じ警告を毎分出さない。 ## 7.4 Staff算入 Staffが次を全て満たす場合だけactive_staffへ算入する。 - WORKING状態 - 施設access pointへ到着済み - 対応シフト - Health 1以上、Stamina 1以上 - 必要Roleまたは代替許可 - 他施設に排他的に算入されていない staff_min未満では新規サービスを開始しない。進行中サービスは安全に終了する。 ## 7.5 ServiceRequest - request_id - guest_ids - facility_id - service_type - created_at - quoted_price - policy_snapshot - reserved_inventory_ids - preference_match - expected_quality - expected_wait - priority 価格とポリシーはRequest作成時に固定する。行列中に値上げして既存客へ適用しない。 ## 7.6 QueueEntry 標準はpriority、enqueued_at、request_idの順。FIFO施設ではpriorityを全て0にする。 - queue_capacity超過時は予約を拒否する。 - 入列時に予測待ち時間を客へ保存する。 - 実待ちが予測の2倍を超えた時点で再意思決定を許可する。 - 客が離脱したEntryは即時削除。 - Entryのguest_idが存在しない場合は破損Entryとして削除しログを残す。 ## 7.7 ServiceInstance - service_id - request_id - started_at、finish_at - guest_ids、staff_ids - quality_snapshot - price_snapshot - inventory_reservations - interruption_policy 開始時に品質、時間、価格、必要在庫を確定する。通常のStaff交代は進行中サービスを完了してから行う。 ## 7.8 中断 | 原因 | 処理 | | --- | --- | | プレイヤー通常閉鎖 | 進行中は完了 | | Mana停止 | 安全中断し、再開可能 | | Staff気絶 | 代替Staffを5分待ち、いなければ失敗 | | 暴動・火災 | 即時避難、失敗 | | 客の任意離脱 | 利用割合に応じ部分精算 | | 施設破損 | 即時停止、原則返金 | 返金と在庫返却はTransactionで行う。消費済み消耗品は返却しない。 ## 7.9 完了 1. guestとstaffの存在を確認。 2. 予約在庫を消費。 3. 支払いTransactionをCommit。 4. 欲求、満足、依存、疲労を変更。 5. 汚れ、騒音、Heatを発生。 6. 統計を更新。 7. GuestをDECIDINGへ。 8. 次Queueを繰り上げ対象にする。 ## 7.10 宿泊 客室は通常Serviceと異なる長期占有を行う。 - チェックイン時にbed_slotを予約。 - 基本1泊は当日20:00~翌08:00。 - 遅いチェックインでも08:00までを1泊とする。 - 延泊判断は翌06:00。 - 清掃完了まで次客へ割り当てない。 - 客が外出中でもbed_slotを保持する。 - 強制退店時は所有物をLostAndFoundへ移す。 ## 7.11 Policy Policy値は型、範囲、解禁Level、変更猶予を定義する。 - 価格: 即時。ただし新規Requestのみ。 - 客層制限: 新規入場のみ。 - 混浴など運営形式: 利用者0の時だけ変更。 - 高危険営業: 次の30分境界で適用。 - 避妊等の成人向けPolicy: Service開始時Snapshotへ保存。 Policy変更の予測差分をUIへ返す。 --- # 8. Inventoryと物流 ## 8.1 ItemDefinition - id、category - max_stack - unit_weight - quality_range - shelf_life_minutes - purchase_price、base_sell_price - legal_status - storage_tags - consumption_tags ## 8.2 InventoryStack - stack_id - item_id - quality - quantity - reserved_quantity - created_at - expires_at - owner_inventory_id 不変条件: - quantityは0以上。 - reserved_quantityは0以上quantity以下。 - quantity 0のStackはTick終了時に削除。 - 期限なしはexpires_atを-1とする。 ## 8.3 Reservation - reservation_id - stack_id - quantity - requester_id - purpose - created_at - expires_at 予約はInventoryServiceだけが作成・解放する。予約分は他Requestが利用できない。 ## 8.4 LogisticsRequest - request_id - source_inventory_id - destination_inventory_id - item_requirements - priority 0~100 - created_at - deadline - reserved_stack_ids - assigned_carrier_id - state - retry_count State: OPEN、RESERVED、ASSIGNED、PICKING_UP、DELIVERING、COMPLETED、BLOCKED、CANCELLED。 ## 8.5 Dispatcher 未割当Requestごとに候補運搬員を評価する。 DispatchScore = priority × 10 - sourceまでの移動分 × 3 - 現在荷物の迂回分 × 2 + Role適性 + deadline危険度 最高Scoreを割り当てる。同一運搬員へ容量を超えて割り当てない。 ## 8.6 運搬 - 標準運搬容量は重量10。 - Operation 10ごとに容量+1、最大20。 - source到着時に予約在庫を荷物Inventoryへ移す。 - destination到着時に施設Inventoryへ移す。 - 経路切断時は別経路を試し、3回失敗でBLOCKED。 - 運搬員気絶時は荷物をその場のCargoObjectとして残す。 - CargoObjectは別運搬員が回収できる。 ## 8.7 期限切れ - expires_at到達時、未予約分を廃棄物へ変換。 - 予約中StackはService開始前に期限を再確認。 - 期限切れ品の提供を許すPolicyは明示的な高危険設定とし、Quality、事故、Heatへ影響。 - 自動廃棄にはゴミ搬送Requestを発行する。 ## 8.8 自動発注 施設または倉庫ごとに以下を設定する。 - item_id - reorder_point - target_quantity - max_unit_price - preferred_supplier available_quantityがreorder_point未満なら、target_quantityとの差を発注する。未到着注文もavailableへ加算し、重複発注を防ぐ。 --- # 9. Mana ## 9.1 ManaNetwork Mana接続は歩行Graphと別Networkで管理する。 - Source: 世界樹、Mana炉、蓄電設備 - Consumer: 施設、昇降機、照明、ワープ - Connector: 配線、結晶中継 - Storage: 現在値と容量 ## 9.2 供給 10ゲーム分ごとに、Network Component単位で計算する。 NetMana = 生成量 - 必須Consumer - 通常Consumer - 贅沢Consumer 不足時はpriorityの低いConsumerから停止する。同priorityではfacility_id昇順で安定化する。 ## 9.3 Brownout - 供給率75%以上: DEGRADED、処理速度低下。 - 75%未満: NO_POWER。 - 医務、非常灯、避難用昇降機は必須priority。 - 停電から復旧後、施設は次の1分Tickで再評価する。 - 電源のON/OFF振動を防ぐため、停止後10分の最小保持時間を設ける。 --- # 10. 客AI ## 10.1 GuestDefinition 客種別の静的定義は次を持つ。 | 項目 | 型 | 内容 | |---|---|---| | guest_type_id | StringName | 種別ID | | tags | Array[StringName] | 一般、富裕、冒険者、行政関係者など | | wealth_min/max | int | 初期所持金範囲 | | base_patience | int | 標準待機許容分 | | risk_tolerance | int | 0〜100 | | preference_weights | Dictionary | 施設タグ別選好 | | schedule_profile | StringName | 来訪時間帯 | | group_size_weights | Dictionary | 1〜4人の重み | | legal_sensitivity | int | 違法性への忌避 | | quality_sensitivity | int | 品質への反応 | 成人向け区画を利用できる客Definitionはadult_verified=trueを必須とする。未設定またはfalseの客は候補施設抽出段階で完全に除外する。 ## 10.2 GuestRuntime 客個体は最低限次を保持する。 - guest_id - guest_type_id - group_id - wallet_gold - current_cell - state - state_entered_at - target_facility_id - current_service_id - needs: hunger、sleep、hygiene、medical、leisure、intimacy - satisfaction_total - satisfaction_samples - patience_remaining - debt_gold - suspicion - addiction_by_tag - visited_facility_ids - path_handle - planned_exit_at Need、満足度、疑念、依存度は0〜100で固定小数点整数とする。 ## 10.3 入場生成 入場候補数は30ゲーム分ごとに算出する。 ExpectedArrivals = 基礎流入 × 時間帯係数 × 評判係数 × 収容余力係数 × イベント係数 × Heat抑制係数 端数は確率抽選ではなく、流入種別ごとのremainderへ繰り越す。これによりセーブ・ロード後も人数が一致する。 生成条件: 1. 正面入口が到達可能。 2. 建物内の有効客数がpopulation_cap未満。 3. 客種別に適合する最低1施設が存在する。 4. 閉館または避難状態でない。 生成失敗は消費せず、次の評価周期へ持ち越す。ただし3周期連続失敗したgroupは破棄してログを残す。 ## 10.4 Need更新 各Needは1ゲーム分ごとにDefinitionのrateで増減する。正式IDは次の6種だけとする。 - hunger: 移動中+2、待機中+1、食事Serviceで減少。 - sleep: 移動中+2、作業的活動中+1、宿泊・休憩Serviceで減少。 - hygiene: 整数Context `dirt` 0〜100を加算し、浴場・洗面Serviceで減少。 - medical: 整数Context `injury` と `illness` 0〜100を加算し、診療Serviceで減少。 - leisure: 無刺激時+1、娯楽Serviceで減少。 - intimacy: `is_night=false`では整数Context `intimacy_day`、`is_night=true`では`intimacy_night`を加算する。Context増加は`adult_verified=true`のDefinitionにだけ適用する。 `need_effects`は上記6 IDの部分Mapとし、値は符号付き整数だけを許す。不明ID、旧ID、暗黙の数値変換を拒否し、更新前の状態を維持する。複数効果は全て加算してから各Needを一度だけ0〜100へClampする。 `safety`はNeedではない。危険は候補の`risk`/`risk_level`と`risk_tolerance`による除外・Utility罰、重大事故・戦闘・避難命令によるFLEEING等の強制状態遷移で扱う。`social`もNeedではなく、`preference_weights`、施設タグ、`group_fit`、同行者・会話・関係Eventの文脈で扱う。危険や交流の値をMedicalへ加算してはならない。 Need更新には上限・下限Clampを必須とし、複数効果は加算してから一度だけClampする。 ### 10.4.1 GuestService保存互換 GuestService Snapshot Version 2はDefinitionの`need_rates`と各Guestの`needs`に、正式6 IDが過不足なく一度ずつ存在することを要求する。未知Key、欠落Key、旧新混在、非整数値はSnapshot全体を原子的に拒否する。 Version 1の旧5 IDは読込境界でだけ次へ純粋変換する。 | Version 1 | Version 2 | |---|---| | hunger | hunger | | rest | sleep | | fun | leisure | | social | intimacy | | safety | medical | | なし | hygiene = 0 | この`safety → medical`は既存Saveを失わないための保守的な一度限りの移行であり、新規データ上の意味同一性を示さない。Version 1 Mapも旧5 IDの完全一致を要求し、混在Mapを推測で修復しない。移行成功後はVersion 2だけを出力する。 ## 10.5 状態機械 標準状態: SPAWNING → ENTERING → DECIDING → MOVING → QUEUING → RECEIVING_SERVICE → DECIDING 退出系: 任意状態 → LEAVING → DESPAWNING 例外系: - MOVING → REPLANNING: 経路無効。 - QUEUING → ABANDONING: 忍耐切れ。 - RECEIVING_SERVICE → INTERRUPTED: 施設停止。 - 任意状態 → FLEEING: 重大事故、戦闘、避難命令。 - 任意状態 → INCAPACITATED: 体力0。 状態遷移は同一Tick中に最大4回までとする。超過時はDECIDINGで停止し、AI_LOOP_GUARDを記録する。 ## 10.6 行動候補抽出 DECIDING時は次の順で候補を作る。 1. 緊急退出、治療、避難。 2. 未払い、予約、同行者などの強制行動。 3. Needを解消する施設。 4. 任意消費、観光、交流。 5. 退出。 施設候補は次を全て満たす必要がある。 - 客の利用権限とContent Filterに適合。 - 営業中または到着予測時刻に営業予定。 - 入口まで到達可能。 - 予想総額を支払えるか、明示的に信用取引を許す。 - 見積待ち時間がHard Limit未満。 - 客が拒否する危険度、違法度、品質下限に抵触しない。 ## 10.7 Utility評価 候補aの効用は整数で計算する。 Utility(a) = NeedRelief × 8 + Preference × 5 + ExpectedQuality × 3 + Novelty × 2 + GroupFit × 2 - PriceBurden × 5 - TravelMinutes × 2 - WaitMinutes × 3 - Risk × RiskSensitivity - RepetitionPenalty 同点時は次の順で決める。 1. 緊急度が高い。 2. 移動分が短い。 3. facility_id辞書順。 最適候補のUtilityがexit_threshold未満なら退出を選ぶ。ランダムな揺らぎを使う場合もguest_decision Streamから-5〜+5だけ加算し、候補ごとの評価順に依存しないよう候補ID順で乱数を割り当てる。 ## 10.8 移動と再判断 - MOVING開始時にPathRequestを発行する。 - ETAがplanned_exit_atを越える施設は選択しない。 - 入口到着時に営業、価格、Queue、権限を再検証する。 - 再検証失敗時は料金を引かずDECIDINGへ戻す。 - REPLANNINGが3回失敗した場合、案内地点、入口、緊急退場地点の順で復旧する。 ## 10.9 Queue忍耐 初期忍耐: Patience = base_patience + 快適設備補正 + 案内品質補正 - 混雑補正 - fatigue補正 - 過去の失敗補正 待機1分ごとに1減る。列が進んだ、案内を受けた、無料補償を得た場合は回復できる。0到達時、Queueから原子的に除外しABANDONINGへ遷移する。 ## 10.10 支払いと満足度 Service開始時に事前決済、完了時に後払いを行う。施設Definitionで方式を固定する。 SatisfactionSample = 提供品質 + 接客品質 + 環境快適度 + 価格納得度 - 待ち時間罰 - 中断罰 - 危険・目撃罰 結果は0〜100にClampし、客の累積満足度と施設の直近評価へ記録する。返金は元取引IDを参照する逆取引とし、履歴を消さない。 ## 10.11 借金・依存・事故 - 信用取引を許す施設だけがwalletを超える決済を行える。 - debt_goldが客別上限を超える追加提供は禁止。 - 依存度は対象tagのService完了時だけ増え、時間と治療で減る。 - 高依存はUtilityと忍耐へ影響するが、必ずContent Filterと権限判定の後に評価する。 - 意識喪失、暴力、未払い、違法行為はIncidentを生成し、施設内で直接解決しない。 ## 10.12 退出 退出理由: - 予定時刻到達 - 所持金不足 - Need充足 - 不満、危険、避難 - 宿泊終了 - 行政措置 出口到達時に客サマリーを集計し、未解決の借金・事件・予約だけを永続データへ移す。通常の個体RuntimeはDESPAWNING後に解放する。 --- # 11. 従業員AI ## 11.1 StaffDefinitionとRuntime 静的定義: - role_id - permitted_task_tags - base_wage - skill_growth_table - uniform_tags - legal_permissions - equipment_requirements 個体Runtime: - staff_id - role_id - current_cell - assigned_facility_id - shift_id - state - current_task_id - skills - fatigue - stress - morale - loyalty - health - carried_inventory_id - path_handle - absence_reason 各心理値は0〜100、skillは0〜999とする。 ## 11.2 シフト 標準シフト: - DAY: 06:00〜18:00 - NIGHT: 18:00〜06:00 - FLEX: 開始・終了を個別指定 日付を跨ぐシフトは開始日のshift_instance_idで管理する。開始30分前から通勤状態に入り、勤務地点へ向かう。 一日だけの変更はScheduleOverrideとして保存し、基礎契約を書き換えない。 ## 11.3 勤務開始判定 勤務開始条件: 1. 生存し、拘束・重傷・休職でない。 2. シフト時刻内。 3. 勤務入口または担当施設へ到達可能。 4. 必須装備を所持またはロッカーから取得可能。 遅刻はshift開始から15分で確定し、原因をPATH_BLOCKED、NO_EQUIPMENT、PERSONAL、INCIDENTに分類する。 ## 11.4 Task Taskは次を持つ。 - task_id - task_type - priority: 0〜100 - created_at - deadline_at - target_entity_id - target_cell - required_role_tags - required_skill_min - required_items - estimated_minutes - interrupt_level - state - assigned_staff_id - retry_count Task State: OPEN → RESERVED → MOVING → WORKING → COMPLETED 例外はBLOCKED、CANCELLED、FAILED。 ## 11.5 Dispatcher 5ゲーム分ごと、または高優先Task発生時に実行する。 StaffTaskScore = task.priority × 10 + skill_fit × 4 + role_fit × 100 - travel_minutes × 3 - fatigue × 2 - current_task_interrupt_cost - overtime_penalty 割当順はpriority降順、deadline昇順、task_id昇順。Staff側同点はstaff_id昇順とする。 予約はTaskとStaffの両方を一つのTransactionで更新する。必要物資のReservationに失敗したTaskはBLOCKEDへ送る。 ## 11.6 割込み 割込みLevel: - 0: 割込み不可。治療、決闘、危険物処理など。 - 1: 緊急Taskだけ割込み可。 - 2: 同等以上のpriorityで割込み可。 - 3: 常時再配分可。 割込み時は作業進捗をTask定義に従い保持または破棄し、使用前物資だけReservationへ戻す。移動済み物資を瞬間的に元在庫へ戻してはならない。 ## 11.7 状態機械 OFF_DUTY → COMMUTING → AVAILABLE → MOVING_TO_TASK → WORKING → AVAILABLE 補助状態: - BREAKING - EATING - SLEEPING - SEEKING_MEDICAL - FLEEING - INCAPACITATED - STRIKE - DETAINED 健康・安全状態は業務状態より優先する。シフト終了時も、interrupt_level 0のTaskは安全な中断点まで継続する。 ## 11.8 疲労・ストレス・士気 - fatigue: 勤務、移動、徹夜で増加し、睡眠・休憩で減少。 - stress: 事故、危険、過密、未払い、関係悪化で増加。 - morale: 賃金、食事、居住、成功、管理方針で変化。 - loyalty: 長期的な待遇と関係イベントで変化し、短時間では急変させない。 閾値: - fatigue 70: 作業速度-20%。 - fatigue 90: 危険Task禁止、休憩Taskを強制。 - stress 80: 事故率上昇、退職検討。 - morale 20未満かつ賃金滞納: STRIKE候補。 ## 11.9 Skillと品質 作業品質: WorkQuality = 基礎品質 + 対応Skill補正 + 設備補正 + 士気補正 - fatigue罰 - stress罰 - 難度罰 Skill経験はTask完了時にのみ付与する。放置による無限訓練を防ぐため、同一低難度Taskから得られる一日経験に上限を置く。 ## 11.10 欠勤・退職・救助 - health 0はINCAPACITATEDとなり、医療または救助Taskを生成。 - 入口への経路が断たれた勤務者は位置を保ったまま救助対象とする。 - 退職判定は日次精算後に行い、勤務中に突然削除しない。 - 退職者は所持品返却、未払い計上、関係ロック解除を完了してから退場する。 - 行方不明のStaff参照がTaskに残った場合、Reservationを解除してOPENへ戻す。 --- # 12. 関係性システム ## 12.1 RelationshipState 関係主体A・Bごとに次を保持する。 - relationship_id - actor_a_id - actor_b_id - affinity: -100〜100 - trust: 0〜100 - fear: 0〜100 - obligation: -100〜100 - jealousy: 0〜100 - shield_current: 0〜shield_max - shield_max - route_lock - last_event_at - daily_attack_count - flags actor_a_idとactor_b_idは辞書順に正規化し、同じ組み合わせの重複生成を禁止する。方向性が必要な値はa_to_b、b_to_aの子構造へ分ける。 ## 12.2 Shield Shieldは関係イベントへ到達するための保護値で、直接affinityを意味しない。 - shield_maxは対象者の地位、警戒、組織保護、進行度から算出。 - shield_currentは一日の自然回復、警護、イベントで増減。 - 0未満にはならず、shield_maxを超えない。 - 0到達はイベント解禁条件の一つであり、自動成功ではない。 ShieldAttackはCommandとして実行し、Action Point、費用、対象可視性、Cooldownを検証する。 ## 12.3 Shield攻撃結果 Damage = action.base_damage + owner_stat_bonus + intel_bonus + event_bonus - target_resistance - repeated_action_penalty 最低0、最大はaction.max_damage。結果はShieldへ適用した後、Heat、関係値、反撃Triggerを同一Transactionで記録する。 同じActionの一日使用回数には上限を設ける。反復ペナルティは1回目0%、2回目25%、3回目50%、4回目以降75%。 ## 12.4 閾値とイベント解禁 イベント条件は例として次をAND/ORで組み合わせる。 - shield_current <= N - affinity >= N - trust >= N - fear >= N - 指定flagの有無 - 時間帯、場所、施設状態 - 先行イベント完了 - actorが利用可能 閾値を跨いだ瞬間に即時再生せず、EventServiceへ候補を登録する。これにより割込み禁止場面と同時発火を制御する。 ## 12.5 Route Lock route_lock: - NONE - SOFT: 警告を出すが別ルートを進められる。 - EXCLUSIVE_PENDING: 確定確認待ち。 - EXCLUSIVE_LOCKED: 排他的ルート確定。 - CLOSED: 対象ルート終了。 排他確定は明示的な確認Commandまたは仕様に明記された不可逆イベントでのみ行う。Lock変更はセーブされ、ロード時に整合性検査する。 ## 12.6 Event同時実行制御 - 同一actorは原則1つのforeground eventだけに参加できる。 - background eventは状態値だけを変更し、台詞UIを占有しない。 - Eventがactorを確保する際はactor_id昇順でLockを取得する。 - Lock取得失敗時は開始せずQueueへ戻す。 - Event終了、取消、ロード復旧でLockを必ず解放する。 ## 12.7 所有者Action 所有者の対人Actionは次の共通形式を持つ。 - action_id - AP cost - Gold cost - required_location - target_filter - cooldown - risk - Shield/affinity/trust/fear効果 - success formula - failure consequences Action実行時に結果Previewを表示するが、秘密補正や乱数結果そのものは開示しない。 ## 12.8 対決 対決はラウンド制のCommand列として処理する。 Initiative = speed + situational_bonus + rng(0..9) HitScore = skill + equipment + status - target_defense Damage = max(0, base_damage + margin_bonus - armor) - 各ラウンド開始時に参加者が有効か確認。 - 戦闘不能、降伏、逃走、規定ラウンド到達で終了。 - 結果はhealth、関係、Heat、Incidentへ記録。 - 演出中にSimulationを停止しても、決定済み結果は二重適用しない。 ## 12.9 Content Filter Content Filterは表示だけでなく候補抽出、イベント選定、施設利用権限へ適用する。 - NONE: 制限対象コンテンツを許可。 - FADE: 行為詳細を省略し、結果だけ処理。 - REPLACE: 非性的・非残虐な代替イベントへ置換。 - DISABLE: 候補自体を生成しない。 REPLACE対象のEventDefinitionはreplacement_event_idを必須とする。置換先が欠落した場合はDISABLEとして安全側に倒す。 すべての登場人物は成人として定義し、年齢不明・未確認の人物を成人向け候補へ含めない。 --- # 13. 経済・台帳 ## 13.1 LedgerEntry Goldの増減は全てLedgerEntryを経由する。 - transaction_id - absolute_minute - debit_account - credit_account - amount - category - source_entity_id - related_transaction_id - memo_key amountは正整数。取消は削除ではなく、debitとcreditを逆転した新規Entryを作る。 ## 13.2 Account 最低限のAccount: - PLAYER_CASH - GUEST_WALLET:{guest_id} - STAFF_PAYABLE:{staff_id} - STAFF_WALLET:{staff_id} - FACILITY_REVENUE:{facility_id} - SUPPLIER_PAYABLE - TAX_PAYABLE - DEBT_RECEIVABLE:{actor_id} - SYSTEM_SINK - SYSTEM_SOURCE SYSTEM_SOURCE/SINKは初期化、生成、破棄用途に限り、通常運用の収支を隠す目的で使わない。 ## 13.3 不変条件 - 各transactionのDebit合計とCredit合計は一致。 - Gold残高を直接代入しない。 - amount 0のEntryを作らない。 - 同じidempotency_keyのCommandは一度だけ記帳。 - 負残高を許すAccountはDefinitionで明示。 ## 13.4 Service決済 事前決済: 1. 料金を再計算。 2. 支払能力確認。 3. 在庫Reservation確認。 4. Ledger記帳。 5. Service開始。 4または5が失敗した場合はTransaction全体をRollbackする。 後払いはService完了後に請求する。支払不能時は許可された信用枠へ振替え、不可ならIncidentを作る。完了したService効果を巻き戻してはならない。 ## 13.5 価格 FinalPrice = base_price × policy_multiplier × demand_multiplier × quality_multiplier × event_multiplier 各Multiplierは百分率整数で保持し、計算途中は切り捨てず最後に四捨五入する。最低価格0、上限はDefinitionのmax_price。 価格変更は新規QueueEntryにだけ適用し、既に表示・予約された見積にはquote_expires_atまで旧価格を保証する。 ## 13.6 賃金 - 出勤分数、Role単価、残業、危険手当を日次で集計。 - 06:00の日次締めで、直前の営業日06:00~翌05:59分をSTAFF_PAYABLEへ計上。 - 支払可能なら自動支給し、不足分はarrearsへ残す。 - 滞納はmorale、stress、退職、監査へ影響。 - 退職しても未払い債務は消去しない。 ## 13.7 日次締め 営業日は06:00から翌日05:59までとし、日次締めは翌06:00に次の順で行う。 1. 前日未確定Serviceの確定。 2. 売上、返金、仕入、賃金、税、罰金の集計。 3. Account残高照合。 4. 施設別損益生成。 5. 異常Entry検出。 6. DaySummary保存。 照合差額が0でなければ自動補正せず、SimulationをPauseしてLEDGER_IMBALANCEを通知する。 MVPの日次分類は`revenue`、`refunds`、`cost_of_goods`、`wages`、`maintenance`、`utilities`、`taxes_and_interest`、`fines`、`construction_cash_flow`に固定する。通常の日給はStaffの当日発生額を`wages`へ一度だけ計上し、過年度・前日からの未払返済を再度費用化しない。雇用契約時の`STAFF_SIGNING_COST`は日給と分離して`cost_of_goods`へ計上する。 `OPERATING_REFUND`は客への返金ではなく運営費の戻しであるため、同一期間の運営費を相殺する。相殺額が期間内運営費を超える場合、超過分だけをexpense recoveryとして`revenue`へ入れ、`OPERATING_REFUND_EXCEEDS_PERIOD_COST`を異常欄へ残す。客への`REFUND`は`refunds`へ計上する。分類表にないcategoryは暗黙推測せず締めを失敗させる。 ## 13.8 需要 需要は施設タグ×客種別×時間帯で管理する。 DemandIndex = 基礎需要 + 評判補正 + 未充足Need補正 + イベント補正 - 直近価格上昇罰 - 待ち時間罰 0〜200へClampする。需要は流入人数とUtilityへ影響するが、客の所持金を増やさない。 --- # 14. 行政・監査・防衛 ## 14.1 AuthorityState - heat: 0〜100 - suspicion: 0〜100 - evidence_points: 0以上 - inspection_level: 0〜5 - next_inspection_window - active_case_ids - fines_due - known_illegal_facility_ids Heatは目立つ行為への短期反応、suspicionは継続的な疑い、evidenceは監査中に確保された証拠とする。 ## 14.2 Heat更新 HeatDeltaはIncident、違法施設稼働、騒音、目撃、賄賂失敗、戦闘から発生する。日次で自然減衰するが、active_caseがある間は最低値を下回らない。 同一Incidentを複数の検知器が報告してもincident_idで重複排除する。隠蔽ActionはHeatを直接0にせず、証拠または公開度へ作用する。 ## 14.3 監査抽選 06:00に、監査Window内かつ監査未実行なら評価する。 InspectionChance = base_chance + heat × heat_weight + suspicion × suspicion_weight + overdue_case_bonus - reputation_protection 0〜95%へClampする。確率判定にはauthority Streamを使い、失敗しても次回Windowを更新する。 ## 14.4 Inspection State SCHEDULED → ARRIVING → CHECK_IN → PATROLLING → REVIEWING → RESOLVING → DEPARTING → CLOSED 例外: - BLOCKED_ENTRY - INTERRUPTED - HOSTILE - CANCELLED 監査官は通常客と別Entityだが、同じPathServiceを使う。瞬間的な全マップ走査は禁止する。 ## 14.5 巡回経路 監査官は次を候補地点にする。 - 申告対象施設 - 入口から視認できる異常 - suspiciousタグ付き施設の推定位置 - Incident発生地点 - 帳簿・倉庫・従業員設備 PatrolUtility = suspect_score × 5 + evidence_potential × 4 - travel_minutes × 2 - time_budget_penalty 検査時間には上限を置き、全候補を必ず回るとは限らない。 ## 14.6 視認と証拠 発見には次のいずれかが必要。 - 対象Cellへ到達し検査する。 - 公開Layerから視線が通る。 - 信頼できる証言・帳簿矛盾がある。 - 事前にknown_illegalとして特定済み。 壁越し・階層越しに無条件発見しない。証拠はevidence_id、source、severity、facility_id、expires_atを持つ。 ## 14.7 解決 監査結果は証拠点と違反Definitionから決める。 - NO_ACTION - WARNING - FINE - TEMPORARY_CLOSURE - CONFISCATION - ARREST_ATTEMPT - RAID_ESCALATION 罰金はLedgerへ、営業停止はFacility状態へ、押収はInventory Transactionへ記録する。結果適用後に監査Eventを再実行して二重処罰しないようresolution_idを保存する。 ## 14.8 プレイヤー対応 対応Action: - 書類提示 - 施設停止・是正 - 交渉 - 賄賂 - 隠蔽 - 退去要求 - 抵抗 各Actionは成功率、必要資源、失敗時Heat、法的結果をDefinitionで持つ。選択肢非表示ではなく、実行不能理由を表示する。 ## 14.9 防衛 Raidまたは襲撃はWave単位で処理する。 - threat_strength - entry_points - objectives - retreat_threshold - max_duration DefenseScore = 警備員戦力 + 設備防御 + 地形補正 + 指揮補正 - 疲労 - 複数入口罰 勝敗だけの一括抽選にせず、参加者、扉、設備、目的物の状態をラウンドごとに更新する。ただし画面外の小規模衝突は同じ式を使った集約解決を許す。 --- # 15. Event・Notification ## 15.1 EventDefinition - event_id - category - priority - conditions - participants - location_rule - start_window - expires_after - foreground - interrupt_policy - choices - automatic_actions - replacement_event_id - once_scope once_scopeはNONE、PER_DAY、PER_ACTOR_PAIR、PER_SAVEを持つ。 ## 15.2 Condition Conditionはデータで表現し、任意コード実行を許さない。 対応Operator: - EQ、NE、LT、LTE、GT、GTE - HAS_FLAG、NOT_HAS_FLAG - IN_RANGE - ALL、ANY、NOT - ENTITY_EXISTS - PATH_EXISTS - TIME_IN_WINDOW Condition評価は副作用を持たず、同じSnapshotに対して同じ結果を返す。 ## 15.3 Action Event Action: - SET_FLAG - ADD_RELATIONSHIP_VALUE - DAMAGE_SHIELD - ADD_LEDGER_ENTRY - ADD_OR_REMOVE_ITEM - CREATE_TASK - CREATE_INCIDENT - SET_FACILITY_STATE - TELEPORT_ENTITY_FOR_RECOVERY - SCHEDULE_EVENT - SET_ROUTE_LOCK 複数ActionはEvent Transactionで適用する。任意Action失敗時の方針をROLLBACK_ALL、SKIP_FAILED、BRANCH_FAILUREからDefinitionに指定する。 ## 15.4 Queue Event候補はpriority降順、期限昇順、event_instance_id昇順で並べる。 - foregroundは一度に1件。 - 緊急Eventは通常Eventを安全点で中断できる。 - 中断不可Event中は期限を最大60分延長できる。 - 期限切れEventはfallback actionを実行するか、理由を記録して破棄。 - 同一Eventの候補再登録にはdeduplication_keyを使う。 ## 15.5 Choice Choiceは表示文、条件、Cost、効果Preview、Actionを持つ。 選択時に条件とCostを再検証する。UI表示後に資金やactor状態が変化した場合、実行せず最新理由を返す。 選択確定にはchoice_instance_idを使い、二重クリック・再送でActionを重複適用しない。 ## 15.6 Notification Notification: - notification_id - severity: INFO、WARNING、CRITICAL - category - title_key - body_key - entity_id - world_cell - created_at - expires_at - count - read - actions 同一category、entity_id、10ゲーム分以内の通知はまとめ、countを増やす。CRITICALは自動Pause Policyの対象にできる。 通知の位置Actionは対象が消えていても最後の既知位置を表示し、無効参照でUIを閉じない。 ## 15.7 自動Pause 設定可能な自動Pause対象: - 監査開始 - Raid開始 - 従業員死亡・重傷 - Ledger不整合 - 主要施設停止 - 関係Route確定前 - セーブ失敗 Pause中もUI Commandは受け付けるがSimulation Tickは進めない。 --- # 16. セーブ・ロード・移行 ## 16.1 保存単位 セーブはDirectory単位とし、最低限次を含む。 - manifest.json - world.bin - thumbnail.png - journal.log manifest: - format_version - game_version - save_id - display_name - created_at_real - updated_at_real - absolute_minute - day - seed - checksum_world - content_pack_ids - enabled_filter_profile world.binは圧縮バイナリで、Runtime State、ID Counter、RNG Stream State、Event Queue、Ledger、未完了Transaction情報を持つ。 ## 16.2 保存対象 必須: - Simulation時刻と速度 - World Cellと構造Graph - Facility、Inventory、Reservation、物流Request - 客の永続対象と現在建物内にいる全GuestRuntime - StaffRuntime、Task、ScheduleOverride - Relationship、Route Lock - LedgerとAccount残高Cache - Authority、Incident、Evidence - Event QueueとActor Lock - RNG Streamのstate/counter - 通知と設定 Path Cache、描画Node、UI選択、再計算可能な集計値は保存しない。 ## 16.3 原子的保存 1. SimulationをTick境界でPause。 2. 不変条件を軽量検査。 3. SaveSnapshotを作る。 4. 一時Directoryへ全Fileを書き出す。 5. world.binのChecksumを計算しmanifestへ記録。 6. 一時Saveを読み戻してHeader検証。 7. 既存Saveをbackupへ移動。 8. 一時Saveを正式名へRename。 9. 成功後にbackupを世代管理。 失敗時は既存Saveを保持し、一時Directoryを診断用に残す。自動で壊れたSaveを上書きしない。 ## 16.4 Autosave - 日次06:00直後。 - 重要Event開始前。 - 重要Event解決後。 - ユーザー設定の実時間間隔。 Autosaveは3世代を標準とする。foreground eventのChoice待ちでも保存可能だが、choice_instance_idとActor Lockを含める。 ## 16.5 Load 1. manifest構文とformat_version確認。 2. Checksum確認。 3. 必要Content Pack確認。 4. Migration適用。 5. Runtime ObjectをID順で生成。 6. 参照を解決。 7. 不変条件検査。 8. 派生GraphとCacheを再構築。 9. 1分進めずSnapshotを公開。 Load中にSimulation Tickを実行してはならない。 ## 16.6 Migration Migrationはformat_version NからN+1への純粋変換として実装し、途中Versionを飛ばさない。 - 元Saveを変更しない。 - Migration前Backupを作る。 - 各Stepに入力・出力Schema Testを持つ。 - Definition ID変更はalias tableで解決。 - 削除Contentは安全な代替Definitionへ変換する。 Migration失敗時は対象Saveを読取専用として残し、失敗Stepと不足IDを報告する。 ## 16.7 復旧 軽微な不整合だけ自動修復する。 自動修復可能: - 消滅Entityを参照するUI Selectionの破棄。 - 存在しないPath Cacheの再生成。 - orphan TaskのOPEN復帰。 - 終了Eventの残存Actor Lock解除。 - 派生Account残高CacheのLedgerからの再構築。 自動修復禁止: - 不足Goldの生成。 - 欠落Facilityの無条件削除。 - Ledger Entryの書換え。 - Route Lockの推測変更。 - Content Filterを弱める変更。 修復内容はjournal.logへ記録し、ロード後にユーザーへ要約する。 --- # 17. データ定義・検証 ## 17.1 データ配置 実装時は概ね次の単位でDefinitionを分離する。 - data/facilities - data/items - data/guests - data/staff_roles - data/tasks - data/actions - data/events - data/incidents - data/authority - data/balance - data/content_filters ファイル形式はGodot ResourceまたはJSONのどちらかに統一し、同種Definitionで混在させない。MVPでは型検査しやすいResourceを優先する。 ## 17.2 共通Schema規則 - IDは小文字snake_caseのASCII。 - 表示文は直接埋め込まずlocalization keyを持つ。 - 数値には単位を名前または説明で明示。 - 時刻はabsolute_minuteまたはday_minute。実秒と混ぜない。 - 割合は0〜100の整数、倍率は百分率整数を標準とする。 - 任意参照は空文字、必須参照は欠落不可。 - 配列順が結果に影響する場合は仕様に順序を明記する。 - Dictionary反復順へSimulation結果を依存させない。 ## 17.3 起動時検証 DefinitionRegistry構築時に次を確認する。 1. ID重複がない。 2. 必須項目が存在し型が一致。 3. 数値が許容範囲内。 4. 参照先IDが存在。 5. Event依存Graphに意図しない循環がない。 6. Recipeの入出力が空でない。 7. Facilityの入口、Zone、Layer、必要Staffが定義済み。 8. replacement_event_idがContent Filter条件を満たす。 9. 成人向け対象はadult_verifiedを持つ。 10. Balance Tableに必要Keyが全て存在。 致命的Errorが1件でもあればNew GameとLoadを無効化し、定義名・項目・原因を一覧表示する。Warningは起動を許すが開発Buildで目立つ表示を行う。 ## 17.4 Content Pack Content Packはpack_id、version、dependency、load_orderを持つ。 - 同一IDの上書きは明示override=trueが必要。 - Core Definitionの安全条件を弱める上書きは禁止。 - Saveに有効Pack一覧を保存。 - 必須Pack不足時はLoadしない。 - 任意装飾Pack不足はplaceholderへ置換できる。 ## 17.5 Balance Table 調整値はコードへ散在させず、用途別Tableへ集約する。 - time_balance - economy_balance - guest_balance - staff_balance - authority_balance - relationship_balance - construction_balance 各Tableはbalance_versionを持つ。既存Saveへ新Balanceを適用する際、現在状態を再初期化せず、次回計算から新値を使う。 --- # 18. 整合性・例外処理 ## 18.1 共通原則 - 失敗を無視して進めない。 - ユーザー起因の失敗はResult Codeと修正方法を返す。 - データ不整合は安全側へ停止し、原因IDを残す。 - 例外復旧でGold、Item、Actorを無から生成しない。 - 同じ復旧を何度実行しても結果が増殖しないよう冪等にする。 ## 18.2 施設参照欠落 FacilityDefinitionが欠落したInstanceはMISSING_DEFINITIONとする。 - 稼働、入場、収入を停止。 - footprintとInventoryは保持。 - placeholder表示で位置を特定可能にする。 - 削除または代替Definition選択をユーザーへ提示。 - 自動売却しない。 ## 18.3 在庫不整合 検査周期ごとに以下を確認する。 sum(reservations) <= stack.quantity 違反時: 1. 新規Reservationを停止。 2. 期限切れ、消滅RequestのReservationを解除。 3. なお超過なら作成時刻の新しいReservationからBLOCKEDへ戻す。 4. quantity自体は増やさない。 5. INVENTORY_RESERVATION_OVERFLOWを通知。 ## 18.4 負在庫 Item減算前にTransactionで数量を再検証する。負数が検出された場合は減算を拒否し、直前のServiceまたは物流完了をFAILEDへする。起動時に負数が存在するSaveは自動で0へ丸めず、復旧画面を出す。 ## 18.5 経路不能 - 客: 再判断後、出口または安全地点へ向かう。 - 従業員: TaskをBLOCKEDへし、救助または建設通知を出す。 - 運搬員: 荷物を保持して待機し、期限後に安全地点へ置く。 - 監査官: 検査不能理由を結果へ記録し、入口へ戻る。 - 施設: 入口到達不能ならUNREACHABLE。 Entityを壁越しに移す復旧は、明示的な緊急Teleportとログがある場合だけ許可する。 ## 18.6 Stuck検出 移動Entityが次の条件を満たすとSTUCK候補。 - pathがあるのに10ゲーム分Cellが変化しない。 - 予約したElevatorへ30分以上乗れない。 - REPLANNINGを3回繰り返す。 復旧順: 1. 現在Pathを破棄し再探索。 2. 混雑Costを一時緩和。 3. 隣接する到達可能Cellへ位置補正。 4. 安全地点へ緊急Teleport。 3以降はWARNING、4はCRITICALログを残す。 ## 18.7 Actor欠落 Event中のActorが死亡、退職、拘束、削除された場合、EventDefinitionのmissing_actor_policyに従う。 - CANCEL - REPLACE_WITH_ROLE - CONTINUE_WITHOUT - BRANCH_FAILURE 未定義時はCANCEL。Actor Lockと予約Costを解放し、既に確定したLedger Entryは逆取引で処理する。 ## 18.8 Event失敗 Action列の途中失敗時はtransaction_policyに従う。Failure Branch自体が失敗した場合は全ActionをRollbackし、EventをERRORへしてSimulationをPauseする。無限再試行を避けるため自動Retryは最大1回。 ## 18.9 Ledger不整合 Debit/Credit不一致、重複transaction_id、未知Account、残高Cache差異を検知する。 - 新規経済Commandを停止。 - SimulationをPause。 - Ledger原本は変更しない。 - Cacheだけは再構築可能。 - 原因範囲をtransaction_idで出力。 ## 18.10 Clock過負荷 描画負荷により1Frameで8 Tickを超える未処理が生じたらbacklogへ積む。backlogが120ゲーム分を超えた時点で自動Pauseし、速度低下を提案する。Tickを捨てたり、複数分を一括して結果だけ適用したりしない。 ## 18.11 Save失敗 - 容量不足: 一時Saveを保持し、必要容量を表示。 - 権限不足: 保存先と権限を表示。 - Checksum不一致: 正式化せず既存Saveへ戻す。 - Serialize不能Entity: entity_idと型を記録し保存失敗。 - Autosave失敗: 手動Saveを妨げないがCRITICAL通知。 ## 18.12 無効Command Command実行中に対象が変化した場合、古いPreviewに基づき強行しない。CONFLICT_SNAPSHOT_VERSIONを返し、UIへ再取得を要求する。GoldやAPを先に引いていた場合は同Transaction内でRollbackする。 --- # 19. 公開インターフェース ## 19.1 Service間規約 各Serviceは次の責務を分ける。 - query: Snapshotまたは読取専用Viewを返す。 - validate: 副作用なしのValidationResultを返す。 - command: CommandQueueへ投入する。 - apply: SimulationKernelだけがTick中に呼ぶ。 - event: 適用後の事実をDomainEventとして公開する。 UI、演出、音声からServiceの内部状態を直接変更しない。 ## 19.2 DomainEvent 共通項目: - domain_event_id - event_type - absolute_minute - source_service - entity_ids - payload - causation_command_id DomainEventは「起きた事実」であり、拒否可能な要求には使わない。Subscriberの処理順は固定し、同じEventを再配送しても二重適用しない。 一つのTransactionが複数DomainEventを生成する場合は、全EventのSchema、ID重複、既発行履歴との衝突を先に検証し、全IDと履歴を一括CommitしてからSubscriberへ配信する。不正な1件が含まれるBatchは通知を1件も配らず、履歴を変更しない。Subscriberが配信中に別Eventを発行した場合、そのEventは現在のBatch全件より後へ並べる。 Service固有signalはRuntime内部の投影補助であり、永続的な外部通知の正本にしない。UI、保存、実績、他Systemの不可逆処理はTick完了SnapshotまたはCommit済みDomainEventだけを購読する。これにより外部Domain rollback時に、途中signalを事実として二重処理しない。 ## 19.3 主要DomainEvent - TIME_MINUTE_ADVANCED - DAY_STARTED - FACILITY_STATE_CHANGED - QUEUE_ENTRY_ADDED - SERVICE_STARTED - SERVICE_COMPLETED - INVENTORY_RESERVED - LOGISTICS_REQUEST_CREATED - TASK_ASSIGNED - GUEST_ENTERED - GUEST_LEFT - STAFF_SHIFT_STARTED - INCIDENT_CREATED - RELATIONSHIP_THRESHOLD_CROSSED - LEDGER_TRANSACTION_POSTED - INSPECTION_STARTED - EVENT_INSTANCE_READY - SAVE_COMPLETED ## 19.4 Snapshot Version SimulationSnapshotはsnapshot_versionを持つ。Command作成時のexpected_snapshot_versionと現在Versionが異なる場合でも、無関係な変更なら再検証後に実行できる。対象Entityのrevisionが変わっている場合はCONFLICTを返す。 各Entityはrevisionを持ち、状態変更Transactionごとに1増やす。 ## 19.5 UI Projection UI向けProjection例: - TopBarProjection - BuildPaletteProjection - FacilityPanelProjection - GuestDetailProjection - StaffRosterProjection - LogisticsProjection - LedgerProjection - AuthorityProjection - NotificationProjection Projectionは表示用に整形済みの値と理由文Keyを返す。UI側で価格、稼働可否、成功率を再計算しない。 --- # 20. 観測・デバッグ・性能 ## 20.1 構造化ログ Log項目: - real_timestamp - absolute_minute - severity - subsystem - code - entity_ids - command_id - message - context 通常PlayではINFOを間引き、WARNING以上を保持する。個人名や台詞全文を診断Logへ不要に含めない。 ## 20.2 Simulation Digest 各ゲーム時間の終端に、次の正規化値からHashを生成する。 - absolute_minute - Entity数と主要State - Account残高 - Inventory数量 - RNG Stream Counter - Event Queue ID - ID Generator Counter 同じ初期SaveとCommand列でDigest列が一致することを決定性Testに使う。 ## 20.3 Debug Overlay 開発Buildで切替可能にする。 - FRONT/BACK Path Graph - 到達不能Cell - Zone、Layer、footprint - 構造支持Graphと危険箇所 - Mana Networkと供給率 - QueueとService Slot - Inventory Reservation - Guest Utility上位候補 - Staff Task割当 - Heat、視認、監査候補 - Actor/Event Lock Overlay表示はSimulation状態を変更しない。 ## 20.4 診断Command 開発専用: - dump_entity entity_id - trace_command command_id - trace_guest_decision guest_id - trace_task task_id - validate_world - validate_ledger - rebuild_derived_graphs - compute_digest - export_balance_snapshot Release Buildでは任意状態変更Commandを無効化する。 ## 20.5 性能目標 基準環境のMVP目標: - 1920×1080、60fpsを目標。 - x1でSimulation CPU平均4ms/frame以下。 - x4でSimulation CPUの95 percentile 12ms/frame以下。 - 有効客200、従業員50、施設150、物流Request200を維持。 - Path探索1件の95 percentile 2ms以下。 - 30秒ごとのAutosaveを除き、Main Thread停止50ms未満。 - 通常Save 5秒以内、Load 8秒以内。 性能不足時もSimulation結果を間引かず、表示更新頻度、Overlay、遠距離Animationを先に削減する。 ## 20.6 更新頻度 | 処理 | 頻度 | |---|---| | Clock、Need、Service進行 | 1ゲーム分 | | Staff Dispatcher | 5ゲーム分または緊急時 | | Mana配分 | 10ゲーム分またはNetwork変更時 | | Guest流入 | 30ゲーム分 | | 構造安全再計算 | Dirty時 | | 需要集計 | 60ゲーム分 | | 日次金融締め、シフト・AP・監査・朝Event・Autosave | 06:00 | | UI Projection | 状態変更時、最大実時間10回/秒 | 全Entityを毎Frame走査しない。ServiceごとのActive Set、Dirty Set、期限Priority Queueを使う。 --- # 21. テスト仕様 ## 21.1 Test階層 - Unit: 式、状態遷移、Validation、Migration。 - Contract: Service公開InterfaceとResult Code。 - Integration: 複数Serviceを跨ぐTransaction。 - Scenario: ゲーム時間を進める一連のPlay。 - Determinism: 同一入力のDigest一致。 - Save/Load: Round Tripと破損復旧。 - Load/Performance: 人数・施設上限付近。 ## 21.2 Simulation Kernel - SYS-TIME-001: x1で実時間30秒後にゲーム内60分進む。 - SYS-TIME-002: x2、x4でもTick欠落がない。 - SYS-TIME-003: Pause中にabsolute_minuteが変わらない。 - SYS-TIME-004: 23:59→00:00では暦日だけが変わり、05:59→06:00で前営業日の日次金融締め、シフト、AP、監査、朝Event、Autosaveが各一度だけ順序通りに行われる。 - SYS-TIME-005: backlog 120分超で自動Pause。 - SYS-TIME-006: catch-up中の最初のTickがPauseを要求すると、同Frameの残りTickを消費しない。 - SYS-TIME-007: 未解決境界またはBANKRUPT中に速度変更・手動stepを行ってもClockと保留分が変わらない。 - SYS-TIME-008: running中の予定Commandとpaused中の即時Commandが同じeffective minute契約を使い、06:00取引を終了済みの前営業日Ledger期間へ入れない。 - SYS-TIME-009: 境界失敗後の再試行で、保留したMana~Snapshot段階だけを同一分に1回実行し、Commandと通知を二重適用しない。 - SYS-RNG-001: 同Seed・同Command列のDigestが一致。 - SYS-RNG-002: Guest乱数消費がAuthority結果へ影響しない。 ## 21.3 建設・世界 - SYS-BLD-001: 重複footprintを拒否。 - SYS-BLD-002: FRONT専用施設をBACKへ置けない。 - SYS-BLD-003: 支持不足Placementに理由Cellを返す。 - SYS-BLD-004: Build開始後に資材消費を二重実行しない。 - SYS-BLD-005: 進捗0%取消は全額、50%超は定義率で返却。 - SYS-BLD-006: 稼働中施設の即時移動を禁止。 - SYS-WLD-001: Rank上昇時だけMap Boundsが広がる。 - SYS-WLD-002: Dirty Region外のEnvironment結果が変化しない。 - SYS-PRG-001: Rankを飛び級・低下できない。 - SYS-PRG-002: 現在Faith消費後も`faith_lifetime`が減らず、達成済みRank条件が巻き戻らない。 - SYS-PRG-003: Rank 4以降は地上・地下の稼働施設を各1以上要求する。 - SYS-PRG-004: World拡張またはPath再構築失敗時にWorld、GameState、Progressionを全て戻す。 - SYS-PRG-005: 同一昇格Requestの再送でWorldを二重拡張しない。 - SYS-PRG-006: 保存された条件値、昇格履歴、派生MapBoundsの改ざんを原子的に拒否する。 ## 21.4 経路・昇降機 - SYS-PATH-001: FRONT客がBACK専用Edgeを使わない。 - SYS-PATH-002: 施設撤去後に該当Cacheだけ失効。 - SYS-PATH-003: Elevator停止中に代替経路へ再探索。 - SYS-PATH-004: 同時RequestのTie Breakが決定的。 - SYS-PATH-005: 30分待ちでStuck復旧が発動。 - SYS-PATH-006: 200客が同一Cellを通過しても永久Deadlockしない。 - SYS-PATH-007: 客用/業務用通路の完成・撤去が対応LayerのROUTEとPathを同時に更新し、失敗時は双方を完全に戻す。 - SYS-PATH-008: 従業員ドアが`staff`権限だけにFRONT↔BACK Cost 120を提供し、客には経路を開かない。 - SYS-PATH-009: 垂直Topologyが未構成の昇降機投影は、ROUTE、Path、Connector、履歴を一切変更せず拒否する。 ## 21.5 施設・在庫・物流 - SYS-FAC-001: NO_STAFFがOPENより優先される。 - SYS-FAC-002: Queue離脱時にService Slotを消費しない。 - SYS-FAC-003: 中断Serviceの返金が1回だけ記帳される。 - SYS-INV-001: 予約合計が数量を超えない。 - SYS-INV-002: 期限切れ予約品をServiceに使わない。 - SYS-INV-003: source到着時の引取で所有Inventoryが切り替わる。 - SYS-INV-004: 運搬員気絶時にCargoが複製されない。 - SYS-LOG-001: 重複自動発注をしない。 - SYS-LOG-002: BLOCKED Requestを解除後に再配分できる。 ## 21.6 Guest AI - SYS-GST-001: 支払不能施設を候補から除外。 - SYS-GST-002: adult_verifiedでない客を制限施設へ入れない。 - SYS-GST-003: Utility同点をfacility_id順で解決。 - SYS-GST-004: Queue忍耐0で原子的に離脱。 - SYS-GST-005: 施設停止時にService中断処理を1回だけ実行。 - SYS-GST-006: 退出後も借金・事件だけが永続化。 - SYS-GST-007: 3回の経路失敗後に安全な退出復旧へ移る。 - SYS-GST-008: 現行Snapshotのneeds/need_ratesが正式6 IDの完全一致でない場合、Live状態を変えず拒否する。 - SYS-GST-009: Version 1の旧5 Needを決定的に移行し、Hygieneを0で補い、以後Version 2だけを出力する。 - SYS-GST-010: DirtだけがHygiene、負傷・病気だけがMedicalへ入り、危険・交流ContextをMedicalへ混ぜない。 - SYS-GST-011: Intimacyの昼夜Context増加は成人確認済みDefinitionにだけ適用する。 ## 21.7 Staff AI - SYS-STF-001: 夜勤が日付を跨いでも同一Shiftとして集計。 - SYS-STF-002: TaskとStaffの予約が片方だけ残らない。 - SYS-STF-003: 割込み不可Taskを通常Taskが中断しない。 - SYS-STF-004: fatigue 90以上で危険Taskを割り当てない。 - SYS-STF-005: 消滅StaffのTaskがOPENへ戻る。 - SYS-STF-006: 退職後も未払い賃金が残る。 ## 21.8 関係性・Event - SYS-REL-001: Shieldが0未満にならない。 - SYS-REL-002: 反復Actionへ段階Penaltyを適用。 - SYS-REL-003: 排他Routeは確認前にLOCKEDにならない。 - SYS-REL-004: 同一actorのforeground Eventが重ならない。 - SYS-EVT-001: choice二重送信でActionを二重適用しない。 - SYS-EVT-002: Actor欠落時にLockとCost予約を解放。 - SYS-EVT-003: REPLACE先欠落時に安全にDISABLE。 - SYS-EVT-004: Event Transaction失敗時に指定Policyで復旧。 ## 21.9 経済・監査 - SYS-ECO-001: 全TransactionのDebitとCreditが一致。 - SYS-ECO-002: 返金が元取引参照の逆取引になる。 - SYS-ECO-003: 見積期限内は価格変更の影響を受けない。 - SYS-ECO-004: 残高不足でも事前決済Serviceが開始しない。 - SYS-AUT-001: 壁越しの違法施設を無条件発見しない。 - SYS-AUT-002: 同一Incidentを証拠へ重複加算しない。 - SYS-AUT-003: resolution_idにより罰金を二重適用しない。 - SYS-AUT-004: 監査官が経路不能時に結果理由を残して退出。 ## 21.10 Save・復旧 - SYS-SAV-001: Save→LoadでSimulation Digestが一致。 - SYS-SAV-002: RNG Stream Counterが復元される。 - SYS-SAV-003: 保存失敗時に以前のSaveが残る。 - SYS-SAV-004: Checksum不一致Saveを実行しない。 - SYS-SAV-005: N→N+2を順次Migrationする。 - SYS-SAV-006: Load直後にゲーム時間が1分進まない。 - SYS-SAV-007: orphan TaskとEvent Lockだけを安全に修復。 - SYS-SAV-008: Content FilterがLoadで弱まらない。 ## 21.11 負荷Scenario PERF-MVP-001: - 客200 - 従業員50 - 施設150 - 開放Cell 20,000 - 同時Queue 500 - 未完了物流Request 200 - Active Event 50 ゲーム内24時間をx4で進め、Crash、永続Deadlock、Ledger不整合、負在庫、120分超backlogがないこと。 ## 21.12 Canonical MVP受入との対応 | GAME_SPECの受入 | 主なSystem Test | |---|---| | MVP-AC-01 14日完走 | SYS-TIME、SYS-GST、SYS-STFと14日Scenario | | MVP-AC-02 混雑改善 | SYS-FAC、SYS-PATH、SYS-GSTと夕方混雑比較Scenario | | MVP-AC-03 BACK動線の効率差 | SYS-PATH、SYS-LOG、SYS-STFと二層Layout比較Scenario | | MVP-AC-04 地下営業の収益とHeat | SYS-ECO、SYS-FAC、SYS-AUTと14日収支比較Scenario | | MVP-AC-05 配置リスクの事前予測 | SYS-REL、SYS-EVTと配置Preview UI Test | | MVP-AC-06 Route確定前の警告 | SYS-REL-003、SYS-EVT-001〜004 | | MVP-AC-07 100客+30従業員の描画性能 | PERF-MVP-001と実描画Frame Test | | MVP-AC-08 Save/Load一致 | SYS-SAV-001〜008 | | MVP-AC-09 二軸構造の理解 | チュートリアル観察Testと初見Playtest | --- # 22. 実装順序 ## S0: 基盤 - DefinitionRegistryとValidation - ID Generator - SimulationKernel - CommandQueueとTransaction - Snapshot、DomainEvent、構造化Log - 決定性Test Harness 完了条件: 同じCommand列を24時間再生しDigestが一致する。 ## S1: 世界・建設 - Cell、Zone、Layer - Occupancy、支持Graph - ConstructionJob - FRONT/BACK Path Graph - Elevator基礎 完了条件: 建設・取消・撤去後もPathと支持状態が整合する。 ## S2: 施設・在庫・物流 - Facility State - Queue、Service Slot - Inventory、Reservation - Logistics RequestとDispatcher - Mana Network - Ledger基礎 完了条件: 原料搬入からService完了・売上記帳まで一周する。 ## S3: 客・従業員 - Guest流入、Need、Utility、退出 - Staff Shift、Task Dispatcher - 疲労、事故、救助 - Notification Projection 完了条件: 24時間無操作Simulationで客と勤務者がDeadlockしない。 ## S4: 関係・Event - Relationship、Shield - Event Condition、Action、Choice - Actor Lock、Route Lock - Content Filter 完了条件: 分岐、保存、再開、置換が決定的に動く。 ## S5: 行政・防衛 - Heat、Incident、Evidence - 監査官AIと巡回 - 結果適用 - Raid、防衛 完了条件: 発見根拠を地図上で追跡でき、二重処罰がない。 ## S6: 保存・運用 - Atomic Save - Autosave世代管理 - Migration - Recovery - Performance BudgetとProfiler 完了条件: 全SYS-SAVとPERF-MVP-001を通過する。 ## 22.1 依存関係 - S1はS0完了後。 - S2はWorld、Transaction、Pathへ依存。 - S3はS2のFacility、Inventory、Ledgerへ依存。 - S4はS0のEvent基盤とS3のActorへ依存。 - S5はS1のPath、S3のAI、S4のEventへ依存。 - S6は各段階からSave対象登録を進め、最後に統合する。 機能を先に作ってSave対応を後回しにせず、各System追加時にSerialize、Digest、Round Trip Testを同時追加する。 --- # 23. MVP後へ延期する項目 次はInterfaceだけ予約し、MVP実装には含めない。 - 複数拠点の同時運営 - Network Multiplayer - User Modの任意Script実行 - 全自動の施設レイアウト最適化 - 物理演算による連続的な建物崩壊 - 数千人規模の群衆Simulation - 経済市場の他都市Simulation - 完全なEvent Sourcingによる任意時点巻戻し - Cloud Save競合Merge 延期項目を理由にMVPのSave SchemaやService境界を過剰に一般化しない。 --- # 24. 実装開始時チェックリスト - [ ] 対象機能のDefinitionとRuntime Stateを分離した。 - [ ] Command、Validation、Result Codeを定義した。 - [ ] 同一Tick内の処理順を決めた。 - [ ] Gold、Item、APをTransaction化した。 - [ ] Path切断、Actor消滅、施設停止時の挙動を決めた。 - [ ] Save対象と派生Cacheを区別した。 - [ ] RNG StreamとTie Breakを固定した。 - [ ] Content Filterと成人確認を候補抽出へ適用した。 - [ ] DomainEventとNotificationを定義した。 - [ ] Unit、Integration、Determinism、Round Trip Testを追加した。 - [ ] Debug Overlayまたは構造化Logで原因を追跡できる。 - [ ] GAME_SPECIFICATION.mdの受入条件へ紐付けた。 このチェックリストを満たさない機能は、画面上で動いていてもSystemとして完了扱いにしない。