Files
TheTree_mock/docs/plans/original-completion-2026-10-02/build_atomic_requirements.py
T
Takeshi b84bade045 Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
2026-10-11 15:58:00 +09:00

735 lines
64 KiB
Python

#!/usr/bin/env python3
"""Build the reviewed source-condition register, without awarding game acceptance.
Only the new P01 deliverables are written. Stable semantic IDs support external evidence joins.
"""
import hashlib, json, subprocess
from datetime import datetime, timezone, timedelta
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
OUT = Path(__file__).resolve().parent
BASE = 'docs/traceability/original/BASELINE.txt'
GROUPS = 'docs/traceability/original/requirements.json'
DECISIONS = 'docs/plans/original-completion-2026-10-02/decisions.json'
SUPPLEMENT = 'docs/traceability/original/audit-2026-09-29/TRACEABILITY.md'
PLAN = 'docs/plans/original-completion-2026-10-02/execution/IMPLEMENTATION_PLAN.initial.md'
VIEWS = ['source_condition','definition','runtime','normal_ui','save_migration','boundary_verification','assets_platform','evidence']
sha = lambda path: hashlib.sha256((ROOT/path).read_bytes()).hexdigest()
pins = {p:sha(p) for p in [BASE,GROUPS,DECISIONS,SUPPLEMENT,PLAN]}
lines = (ROOT/BASE).read_text().splitlines()
groups = json.loads((ROOT/GROUPS).read_text())
coverage_groups = json.loads((OUT/'requirement_coverage.json').read_text())
group_by_id={g['id']:g for g in groups}
records={}; merges=[]
head=subprocess.check_output(['git','rev-parse','HEAD'],cwd=ROOT,text=True).strip()
now=datetime.now(timezone(timedelta(hours=9))).isoformat(timespec='seconds')
normal_entries={
'P00':'タイトル/起動・配布情報','P01':'要件別受入と内容台帳','P03':'保存/読込/終了/再起動',
'P04':'時間経過と日付境界','P05':'通常営業/資金/仕入れ/決算','P06':'マナ管理/通常営業',
'P07':'天候/環境/流行と通常営業','P08':'建設パレット/配置/移動/撤去','P09':'施設詳細/営業管理/通常利用',
'P10':'宿泊/飲食施設の建設と通常営業','P11':'衛生/娯楽/礼拝施設の建設と通常営業',
'P12':'裏層施設の建設と業務','P13':'特殊施設の建設と運用','P14':'通常移動とEV/階段/ワープ',
'P15':'客来店から行動観察','P16':'雇用/シフト/休息/救護','P17':'従業員画面/人物加入/配置/成長',
'P18':'人物詳細/関係/決闘','P19':'人物イベントと分岐','P20':'新規ゲーム/章進行/エンド/図鑑',
'P21':'防衛開始から結果','P22':'素材台帳/必要量/差替仕様','P23':'断面マップとユニット表示',
'P24':'通常画面とインスペクター','P25':'完成ドット素材/施設差分/音を使う通常画面',
'P26':'完成シナリオ/立ち絵/CG/ボイスを使う人物・イベント画面','P27':'同一候補版での要件別受入',
'P28':'新規から本編/全要素と実測','P29':'同一Windows配布版の実機起動/操作/保存再開'}
def add(key, condition, line_nums=None, package='P27', group_ids=None, source=BASE,
authority='designated_original_saved_copy', decision=None, supplement=None,
pending=None, entity=None, scope='feature_or_content', replace=False, acceptance=None):
rid='AR.'+key
line_nums=list(line_nums or [])
if group_ids is None:
group_ids=[g['id'] for g in groups if source==BASE and set(line_nums)&set(g['source_lines'])]
if rid in records:
rec=records[rid]
if rec['condition'] != condition:
merges.append({'kept_id':rid,'alias_source':source,'alias_lines':line_nums,
'alias_condition':condition,'reason':'same specification condition; source refinement is counted once'})
if replace: rec['condition']=condition; rec['acceptance']['condition_assertion']=condition
else:
rec={
'id':rid,'condition':condition,'scope':scope,'mandatory':True,'equal_count_weight':1,
'source_anchors':[],'source_sha256':pins[BASE],'user_amendment_refs':[],
'source_group_ids':[],'supplement_ids':[],'entity':entity,
'source_head':head,'primary_package':package,'package':package,
'work_packages':[package],'responsible_role':'実装担当・素材担当(機能別担当の割当は要再監査)',
'reviewer':{'required_role':'実装者と独立した受入判定者','assigned':None,'assignment_state':'unassigned'},
'normal_entry':normal_entries[package], 'implementation_paths':[],
'save_contract':{'state':'not_reaudited','requirement':'関連状態を保存/終了/再起動で維持し、旧版拒否時は原状を保持する。適用外は判定者が出典付きで判断する。'},
'applicable_completion_views':VIEWS.copy(),'applicability_state':'conservative_pending_review',
'view_results':{v:{'state':'unverified','implementation_state':'not_reaudited','evidence_refs':[],'reviewer':None,'not_applicable_reason':None} for v in VIEWS},
'acceptance':{'condition_assertion':acceptance or condition,
'normal_operation':'指定通常入口から到達し条件の成立を現Runtimeと画面で照合する。',
'persistence':'関連状態の保存/終了/再起動と旧版・欠損・破損拒否を確認する。',
'boundaries':'条件の直前/一致/直後、停止/中断/重複、参照喪失の適用範囲を試験設計する。',
'source_and_assets':'同一採用版のソースSHA、必要完成素材、配布対象を関連づける。',
'evidence_policy':'旧群の状態と過去テスト件数から合格を転記しない。モデル試験、通常操作、Windows実機は区別する。'},
'evidence_refs':[],'historical_evidence_refs':[],
'implementation_state':'not_reaudited','acceptance_state':'unverified','status':'unverified',
'pending_decision_ids':[],'value_contract':None,'decision_dependencies':[], 'difference_state':'not_reaudited','asset_state':'not_reaudited',
'updated_at':now}
records[rid]=rec
anchor={'path':source,'lines':line_nums,'sha256':pins[source],'authority':authority}
if decision: anchor['decision_id']=decision
if supplement: anchor['supplement_id']=supplement
if anchor not in rec['source_anchors']:rec['source_anchors'].append(anchor)
rec['source_group_ids']=sorted(set(rec['source_group_ids']+group_ids))
rec['work_packages']=sorted(set(rec['work_packages']+[package]))
if decision:rec['user_amendment_refs']=sorted(set(rec['user_amendment_refs']+[decision]))
if supplement:rec['supplement_ids']=sorted(set(rec['supplement_ids']+[supplement]))
if pending:
ids=([pending] if isinstance(pending,str) else pending)
rec['pending_decision_ids']=sorted(set(rec['pending_decision_ids']+ids))
for did in ids:
dependency={'decision_id':did,'kind':'informational_explicitly_unset_optional_extension' if did=='D03' else 'required_for_dependent_acceptance','must_not_block_independent_work':True}
if dependency not in rec['decision_dependencies']:rec['decision_dependencies'].append(dependency)
return rec
# A line can contain multiple independently judgeable clauses; every clause below is curated.
# Repeated semantic keys attach another source anchor instead of another denominator unit.
SPEC = '''
3|identity.code|プロジェクトコードはProject Yggdrasil。|P00
4|identity.title|仮タイトルはThe Tree。|P00
5|identity.fantasy_management|ファンタジー世界樹経営シミュレーションのゲームとして成立する。|P20
5|identity.tower_building|タワービルディングのゲームとして成立する。|P20
5,32|identity.age_rating|原本の成人向け区分を台帳と配布設定で管理する。|P29
6|platform.windows|同一Windows配布版でゲームを起動し通常操作できる。|P29|D10
8|progression.main_duration|本編クリアの実プレイ時間は20〜30時間。|P28
9|progression.full_duration|全要素クリアの実プレイ時間は50時間以上。|P28
9|progression.all_endings|全エンドへ通常進行で到達し全エンド取得を判定できる。|P20|D08
9|progression.encyclopedia_complete|図鑑の全対象を取得しコンプリートを判定できる。|P20|D08
10,37,216|world.cutaway|ゲームマップは2Dサイドビューの断面図で表示する。|P23
12|world.tree_setting|地上と地下を同一世界樹として管理する。|P20
12|world.dual_management|地上領域と地下領域を同時に管理・拡張できる。|P07
13|world.autonomous_observation|客と従業員の自律行動を通常画面で観察できる。|P24
13|world.intervention|管理者の介入が人物の進行へ反映される。|P19|D08
14|progression.tree_growth|フロアの積上げに伴い世界樹が成長する。|P20|D04
14|progression.height_authority|高さの成長が表の権威増加へつながる。|P20|D04
14|progression.depth_wealth|地下の深さの成長が裏の富増加へつながる。|P20|D04
16|construction.normal_build|通常操作で施設を建設できる。|P08
16|construction.normal_links|通常操作で通路・昇降設備を配置できる。|P14
16,24|ui.layer_switch|通常UIで表層と裏層を即時に切り替える。|P07
17|guest.diverse_arrival|多様な客が通常営業で来店する。|P15
17,25|guest.autonomous_ai|客は欲求に従い自律的に行動する。|P15
25|staff.autonomous_ai|従業員は全キャラクター自律AIの対象として業務・移動・休息を実行する。|P16
19,26|economy.front_faith|表の健全サービスで信仰を獲得する。|P20|D04
19|service.healing|治療サービスを通常業務として提供する。|P12|D04
19|economy.front_gold|表のサービスで表資金を獲得する。|P05
20,26|guest.underground_addiction|地下のサービス利用で客の依存度が上がる。|P15|D04
20|economy.back_gold|裏のサービスで裏資金を獲得する。|P05
21|staff.back_routes|裏層の動線を確保して職員業務へ接続する。|P16
21,34|underground.blackmail|記録した根拠を使う脅迫取引の状態と収支を管理する。|P19|D08
21,34|underground.trade_people|ユニット売却の状態と収支を管理する。|P16|D04
22|progression.rank_unlock|世界樹ランク上昇で高度な施設を解禁する。|P20|D04
22|defense.repulse|防衛力により外敵を撃退できる。|P21|D04
24,41|world.two_slots|全グリッド座標に独立した表/裏の二スロットがある。|P07
24,42|world.guest_front_visible|客は表層のみを認識する。|P07
24,43|world.guest_back_hidden|客は裏層のユニットと機能を認識しない。|P07
25|traffic.elevator_congestion|昇降機配置が悪いと渋滞が発生する。|P14
25,67,109|staff.shift_day|各施設に昼番06:00〜18:00のスロットを設定する。|P16
25,67,109|staff.shift_night|各施設に夜番18:00〜06:00のスロットを設定する。|P16
26|progression.faith_story|信仰をストーリー進行へ反映する。|P20|D04
26|guest.addiction_all_wealth|依存の進行により原本指定の全財産支出へつながる。|P15|D04
26,195|guest.addiction_budget_priority|依存客は生存費より娯楽費を優先する。|P15|D04
28|narrative.tree_origin|舞台は旧魔王城跡地に生えた世界樹。|P20|D08
29|narrative.protagonist_past|主人公には勇者として使い捨てられた背景がある。|P20|D08
29|narrative.protagonist_goal|宿屋の主として再起し真の王への即位を目指す進行がある。|P20|D08
33|narrative.route_tones|原本3ルートの内容と表現が指定方向性を満たす。内容が未制作なら受入しない。|P19|D08
34|underground.observation|原本の観察機能を状態と通常入口で管理する。|P19|D08
37|world.unit_grid|マップを1ブロック単位のグリッドで管理する。|P07
38|world.above_ground|Y>0を地上(幹・枝)として扱う。|P07
38|world.below_ground|Y<0を地下(根)として扱う。|P07
38|world.ground_boundary|Y=0を地面の境界として扱う。|P07
39,164|construction.support|地面または他の完成済み施設・幹へ隣接して建設する。|P08
39|construction.expand_area|進行に伴い建設可能領域を拡張する。|P20|D04
42|world.front_guests|客ユニットは表層で行動する。|P07
42|world.front_service_staff|接客中の従業員は表層で業務する。|P16
43|world.back_waiting_staff|待機中の従業員は裏層にいる。|P16
43|world.back_moving_staff|移動中の従業員は裏層を使う。|P16
43|world.back_security|警備員は裏層を使う。|P16
43|world.back_goods|運搬物資は裏層を通る。|P12
43|world.back_observer|観察中の管理者状態を裏層へ対応付ける。|P19
44,216|ui.front_back_transparency|表層モードでは裏層を半透明に表示する。|P07
44,216|ui.back_front_wireframe|裏層モードでは表層をワイヤーフレーム表示する。|P07
47|environment.beauty|景観度を計算し客満足度へ反映する。|P22|D04
47|environment.cleanliness|清潔度を計算し客満足度へ反映する。|P22|D04
47|environment.noise|騒音度を計算し客満足度へ反映する。|P22|D04
49|infrastructure.linen_chute|リネンシュートを裏層で設置・運用できる。|P12|D04
49|infrastructure.hidden_door|隠し扉を裏層で設置・運用できる。|P13|D04
50,177|infrastructure.staff_door_crossing|施設奥の職員ドアを通じ表層と裏層を移動する。|P14
50|infrastructure.staff_door_no_guest|客は従業員ドアを通過できない。|P14
52,174|facility.peeping_room.target_placement|観察部屋は指定された表層対象の裏に限定して建設する。|P13
52,174|facility.peeping_room.corridor_loss|観察部屋の占有グリッドは裏通路として利用できない。|P13
53|observation.alternate_view|観察機能には通常マップと異なる専用視点がある。|P19
53|observation.recorder|記録係の配置により根拠データを収集する。|P19|D04
55|environment.ground_light|地上に採光ボーナスがある。|P22|D04
55|environment.ground_mana|地上にマナ濃度ボーナスがある。|P06|D04
55|guest.ground_classes|地上は一般市民・貴族・聖職者向けとなる。|P15|D04
56|environment.underground_sound|地下に遮音ボーナスがある。|P22|D04
56|environment.underground_concealment|地下に隠蔽ボーナスがある。|P22|D04
56|guest.underground_classes|地下は犯罪者・魔物・依存客向けとなる。|P15|D04
59|time.scale|実時間10秒がゲーム内1時間に対応する。|P04
60|time.speeds|Pause/x1/x2/x3の再生速度を選択できる。|P04
60|time.pause_build|Pause中も建設できる。|P08
60|time.pause_commands|Pause中も指示できる。|P24
62|time.morning_phase|朝フェーズは06:00〜10:00。|P04
62|traffic.morning_down_rush|朝に下りラッシュが生じる。|P14
62|guest.morning_checkout|朝にチェックアウトが発生する。|P15
62|guest.morning_breakfast|朝に朝食需要が発生する。|P15
62|staff.morning_arrival|朝に昼番が出勤する。|P16
62|staff.morning_cleaning|朝に清掃業務を実行する。|P16
63|time.day_phase|昼フェーズは10:00〜16:00。|P04
63|guest.day_arrival|昼に新規客が来店する。|P15
63|guest.day_circulation|昼に客が施設を回遊する。|P15
63|time.day_building|昼に営業が安定し建設に適するゲームサイクルが成立する。|P28|D04
64|time.evening_phase|夕フェーズは16:00〜20:00。|P04
64|traffic.evening_up_rush|夕に上りラッシュが生じる。|P14
64|guest.evening_bath_demand|帰還した冒険者が夕に大浴場へ向かう。|P15
64|guest.evening_food_demand|帰還した冒険者が夕に食堂へ向かう。|P15
64|staff.evening_shift_handover|18:00に夜番へ交代する。|P16
64,69|staff.shift_route_congestion|交代時刻の裏層動線が悪いと職員の渋滞により業務が滞る。|P16
65|time.night_phase|夜フェーズは20:00〜06:00。|P04
65|guest.night_sleep|健全客は夜に睡眠行動を取る。|P15
65|guest.night_underground|依存客は夜に地下へ向かう。|P15
65|guest.need_sexual_night_peak|夜に原本の地下サービス欲求解消がピークとなる。|P15|D04
65|events.ntr_night_peak|夜に原本のNTRイベント発生がピークとなる。|P19|D04
68|staff.work_stamina|勤務で体力が減少する。|P16|D04
68,109|staff.rest_stamina|休憩または非番の待機室等で体力が回復する。|P16|D04
68,101,110|staff.zero_collapse|体力0で気絶状態となる。|P16
68,110|staff.back_rescue|気絶者を救護班が裏ルートから回収する。|P16
68|staff.seen_collapse_reputation|客が気絶者を目撃すると評判が低下する。|P16|D04
71|economy.midnight_report|毎日00:00に決算レポートを表示する。|P05
72|economy.settlement_calculation|日次決算で表裏の売上から全対象経費を引く。|P05
72|progression.three_deficits_gameover|赤字決算が3回連続するとゲームオーバーとなる。|P20
74|weather.rain_arrivals|雨で客足が減る。|P22|D04
74|weather.rain_stay|雨で滞在が延長する。|P22|D04
74|weather.storm_no_arrivals|嵐で新規客がゼロになる。|P22
74|weather.fog_magic|マナの霧で魔法効率が上がる。|P22|D04
74|weather.fog_excitation|マナの霧で興奮度が上がる。|P22|D04
75|news.cadence|数日おきに流行ニュースが発生する。|P22|D04
75|news.class_boost|ニュースで特定客層の売上が増える。|P22|D04
75|news.facility_boost|ニュースで特定施設の売上が増える。|P22|D04
78|guest.budget|客は所持金Budgetを持つ。|P15
78|guest.loot|客は戦利品Lootを持つ。|P15
79|guest.need_sleep_range|睡眠欲を0〜100で保持する。|P15
17,79|guest.need_sleep_relief|睡眠欲を客室により解消する。|P15
79|guest.need_sexual_range|原本の地下サービス欲求を0〜100で保持する。|P15
17,79|guest.need_sexual_relief|原本の地下サービス欲求を対象施設で解消する。|P15
79|guest.need_food_range|食欲を0〜100で保持する。|P15
17,79|guest.need_food_relief|食欲を食堂により解消する。|P15
79|guest.need_entertainment_range|娯楽欲を0〜100で保持する。|P15
17,79|guest.need_entertainment_relief|娯楽欲をカジノ等により解消する。|P15
80|guest.patience_loss|渋滞等により忍耐力が減る。|P15|D04
80|guest.zero_patience_exit|忍耐力0で激怒退店する。|P15
80|guest.dirt_contact|汚れを歩くと清潔度が減る。|P22|D04
80|guest.dirt_spread|客が汚れを伝播する。|P22|D04
82|guest.highest_need_destination|現在最も高い欲求を基に目的地を決定する。|P15
83|guest.choice_distance|施設選択の重みに距離を使う。|P15
83|guest.choice_queue|施設選択の重みに行列の短さを使う。|P15
83|guest.choice_affordability|施設選択の重みに価格・クラスと所持金の適合を使う。|P15
85|traffic.astar|経路探索にA*を用いる。|P14
85|traffic.elevator_wait_cost|経路コストへ昇降機の待ち時間を含む。|P14
86|traffic.guest_elevator_capacity|客用昇降機に定員がある。|P14|D02
86|traffic.service_elevator_capacity|裏層の業務用EVは大容量である。|P14|D02
176|traffic.service_elevator_fast|業務用EVは高速に移動する。|P14|D04
86|traffic.stairs_slower|階段は他の垂直移動より速度が遅い。|P14|D04
86|traffic.warp_instant|マナワープで瞬間移動する。|P14
86|traffic.warp_cost|マナワープには大きいコストがかかる。|P14|D04
87|traffic.overlap_half_speed|通路上のユニット重なりで移動速度が50%低下する。|P14
89|guest.addiction_level_three_exit_refusal|依存Lv3で退店を拒否する。|P15
89|guest.addiction_equipment_sale|依存Lv3で装備を売却して滞在を継続する。|P15
90|guest.male_bankruptcy_riot|所持金0かつ高依存の男性客で暴動が起きる。|P15|D04
90|guest.female_bankruptcy_contract|所持金0かつ高依存の女性客で原本の債務ユニット回収イベントが起きる。|P15|D04
92|guest.noble_spending|貴族は金払いがよい。|P15|D04
92|guest.noble_patience|貴族の忍耐力は極端に低い。|P15|D04
93|guest.cleric_underground_exit|聖職者が地下要素を目撃すると即帰宅する。|P15
93|guest.cleric_morality_loss|聖職者が地下要素を目撃すると風紀が下がる。|P15|D04
94|guest.elf_nature|エルフは自然物を好む。|P15|D04
95|guest.demon_underground_tolerance|魔族は地下で満足度が下がらない。|P15
95|guest.demihuman_underground_tolerance|亜人は地下で満足度が下がらない。|P15
98|staff.guild_hiring|一般従業員をギルドから雇用する。|P16
98|staff.daily_wages|一般従業員に日給が発生する。|P05|D04
98|staff.general_stress|一般従業員はストレスが溜まりやすい。|P16|D04
98|staff.general_dismissal|一般従業員を通常操作で解雇できる。|P16
99|staff.market_acquisition|原本の市場から対象ユニットを獲得できる。|P16|D04
99,112|staff.debt_acquisition|破産客から対象ユニットを獲得できる。|P16|D04
99|staff.bound_food_only|原本の拘束ユニットには食費のみが発生する。|P05|D04
99|staff.bound_stress_resistance|原本の拘束ユニットは高いストレス耐性を持つ。|P16|D04
101|staff.stamina|従業員にStaminaを保持する。|P16
101|staff.stress|従業員にStressを保持する。|P16
101|staff.loyalty|従業員にLoyaltyを保持する。|P16
102|staff.skill_operations|料理・清掃に運営技術を用いる。|P16
102|staff.skill_service|接客等に奉仕技術を用いる。|P16
102|staff.skill_combat|警備に戦闘力を用いる。|P16
103|staff.random_traits|生成時にランダム特性を付与する。原本の例示を必須全種と捏造しない。|P16
112|staff.recruitment|通常操作で求人募集できる。|P16
113|staff.dismissal_severance|解雇で退職金を支払う。|P05|D04
113|staff.sale_price_stats|ユニット売却価格はステータスによって変わる。|P16|D04
113|staff.sale_loyalty_risk|ユニット売却により他職員の忠誠度が下がるリスクがある。|P16|D04
116|unique.irreplaceable|ユニーク人物は代替不可能である。|P17
116|unique.hire_free|ユニーク人物の雇用費は無料。|P17
116|unique.upkeep_free|ユニーク人物の維持費は無料。|P05
116|unique.high_growth|ユニーク人物の成長上限は一般従業員より高い。|P17|D04
116|unique.no_dismissal|ユニーク人物を解雇できない。|P17
116|unique.no_sale|ユニーク人物を売却できない。|P17
127|unique.stat_stamina|ユニーク人物に体力を保持する。|P17
127|unique.stat_stress|ユニーク人物にストレスを保持する。|P17
127|unique.stat_operations|ユニーク人物に運営能力を保持する。|P17
127|unique.stat_service|ユニーク人物に奉仕能力を保持する。|P17
127|unique.stat_combat|ユニーク人物に戦闘能力を保持する。|P17
127|unique.stat_underground|ユニーク人物にアングラ能力を保持する。|P17
128|unique.work_exp|業務によってEXPを蓄積する。|P17|D04
128|unique.level_99|ユニーク人物はLv99まで成長する。|P17
130|unique.passive_on_assignment|配置によってパッシブスキルが自動発動する。|P17
131|unique.active_skill|防衛戦等でアクティブスキルを任意発動できる。|P17|D04
132|unique.route_skills|ルート進行度によって習得スキルが変わる。|P17|D08
134|unique.work_warning|長時間勤務の警告を表示する。|P17
134|unique.work_24_hours|警告を無視して24時間勤務できる。|P17
134|unique.work_stress_surge|24時間勤務でストレスが激増する。|P17|D04
134|unique.outfit_aptitude|衣装変更で業務適正補正が変わる。|P17|D04
135|unique.no_normal_loss|ユニーク人物は原則として通常ロストしない。|P17
135|unique.defeat_temporary_absence|防衛敗北で原本の一時離脱状態を管理する。|P21|D08
135|unique.bad_end_transformation|指定バッドエンド確定時だけ原本の人物状態変更を行う。|P19|D08
138|relationship.three_emotions|感情は原本の3パラメータで管理する。|P18|D07
140|relationship.affection_shield_max|好感度が愛の盾の最大耐久値になる。|P18|D07
141|relationship.lust_reduces_defense|原本のLust値上昇で盾の防御力(ダメージ軽減率)が下がる。|P18|D04
142|relationship.ntr_after_break|NTR度は盾破壊後に上昇する。|P18
143|relationship.shield_blocks|愛の盾が有効な間はNTR攻撃を防ぐ。|P18
143,146|relationship.zero_break|盾耐久値0でブレイク状態へ移る。|P18
145|relationship.coassigned_attack|ライバルと同配置で盾耐久値が減る。|P18|D04
147|relationship.contact_after_break|ブレイク後のライバル接触でNTR度が直接上がる。|P18|D04
148|relationship.threshold_shield_loss|NTR度が指定閾値を越えると盾が消滅する。|P18|D04
148|relationship.threshold_love_loss|指定閾値を越えた陥落後は純愛ルートが完全消失する。|P18|D04
151|duel.combat|武力決闘を通常操作で選べる。|P18|D04
151|duel.business|商談決闘を通常操作で選べる。|P18|D04
151|duel.third_type|原本の第三種決闘を通常操作から実行できる。内容と演出が未制作なら受入しない。|P18|D08
152|duel.win_exile|決闘勝利でライバルを追放できる。|P18
152|duel.win_subordinate|決闘勝利でライバルを隷属状態へ移せる。|P18
152|duel.win_full_shield|決闘勝利で愛の盾を全回復する。|P18
153|duel.loss_route_lock|決闘敗北で原本の即時ルート確定状態へ移る。|P18
155|route_action.date_heal|デートで盾を回復する。|P18|D04
155|route_action.talk_heal|会話で盾を回復する。|P18|D04
155|route_action.rival_isolation|ライバルを物理的に隔離して進行を抑止できる。|P18
155|route_action.duel_removal|決闘でライバルを排除して進行を抑止できる。|P18
156|route_action.neglect_weakens|放置により原本の盾弱体化が進む。|P18|D04
156|route_action.underground_weakens|地下業務により原本の盾弱体化が進む。|P18|D04
156|route_action.observation_progress|観察部屋から原本のルート進行状態を確認できる。|P19|D08
157|route_action.item_lust|原本指定アイテムによってLust値が増す。|P18|D04
157|route_action.work_lust|地下業務によってLust値が増す。|P18|D04
157|route_action.shield_cap_loss|原本の堕落進行で盾耐久値上限が減る。|P18|D04
157|route_action.corruption_terminal|原本の堕落終端状態を管理する。|P19|D08
159|history.first_partner|原本の初回相手の履歴フィールドを保持する。|P19
159|history.experience_count|原本の経験数の履歴フィールドを保持する。|P19
159|history.current_partner|現在のパートナーを記録する。|P19
160|history.cycle|原本の周期フィールドを保持する。|P19|D04
160|history.parentage|原本の父親判定を保持する。|P19|D04
160|history.pregnancy_appearance|原本の妊娠期に外見が変わる。|P25|D08
160|history.pregnancy_stats|原本の妊娠期にステータスが変わる。|P19|D04
161,235,242|asset.portrait_ring|純愛ルートの指輪差分を立ち絵へ反映する。|P25|D08
161,235,242|asset.portrait_kiss_mark|NTRルートのキスマーク差分を立ち絵へ反映する。|P25|D08
161,235,242|asset.portrait_sigil|原本の堕落ルート指定の紋差分を立ち絵へ反映する。|P25|D08
161,235,242|asset.portrait_collar|原本の堕落ルート指定の首輪差分を立ち絵へ反映する。|P25|D08
161,235,242|asset.portrait_brand|原本の堕落ルート指定の刻印差分を立ち絵へ反映する。|P25|D08
164|construction.front_access|表層施設は客用通路へアクセス必須。|P08
164|construction.back_access|裏層施設は裏通路へアクセス必須。|P08
165|facility.upgrade_to_max|施設を指定LvMAXまで拡張できる。|P09|D04
165,179|facility.micro_price|レベル上昇で価格設定(高額設定含む)が解禁される。|P09|D04
179|facility.micro_food_rank|レベル上昇で食材ランク設定が解禁される。|P11|D04
179|facility.micro_bath_policy|レベル上昇で原本の浴場ポリシー設定が解禁される。|P11|D04
179|facility.micro_adult_policy|レベル上昇で原本の成人施設ポリシー設定が解禁される。|P13|D08
179|facility.micro_casino_cheat|レベル上昇でカジノのイカサマ率設定が解禁される。|P11|D04
182|economy.front_gold_account|表金を裏金と分けて保持する。|P05
182|economy.back_gold_account|裏金を表金と分けて保持する。|P05
183|mana.energy|マナを施設維持エネルギーとして使う。|P06|D04
183|mana.zero_global_stop|マナ枯渇時に全館機能が停止する。|P06
184|economy.material_construction|建設に資材を使う。|P08
184|economy.material_repair|修理に資材を使う。|P21|D04
186|economy.income_lodging|宿泊料を表の収益へ記帳する。|P05
186|economy.income_food|飲食売上を表の収益へ記帳する。|P05
187|economy.income_adult|原本の成人施設収入を裏の収益へ記帳する。|P05
187|economy.income_casino|カジノ収入を裏の収益へ記帳する。|P05
187|economy.income_blackmail|脅迫金を裏の収益へ記帳する。|P05
187|economy.income_trade|ユニット売却収入を裏の収益へ記帳する。|P05
188|economy.expense_wages|人件費を経費へ記帳する。|P05
188|economy.expense_upkeep|維持費を経費へ記帳する。|P05
188|economy.expense_purchase|仕入れ原価を経費へ記帳する。|P05
188|economy.expense_loan|闇銀行への借金返済を記帳する。|P05
188|economy.expense_rank_tax|世界樹ランク税を経費へ記帳する。|P05|D04
190|economic_facility.reception|受付/精算所をロビーへ設置できる。|P05|D04
190|economic_facility.reception_loss|受付処理が遅いと機会損失が生じる。|P05|D04
191|economic_facility.vault_limit|金庫室で所持金上限を解放する。|P05|D04
191|economic_facility.vault_target|金庫室が盗賊の標的になる。|P21|D04
192|economic_facility.purchase_auto|購買部で消費品を自動発注する。|P05|D04
192|economic_facility.purchase_loot|購買部で戦利品を買い取る。|P05|D04
193|economic_facility.bank_instant|闇銀行から即時融資を受ける。|P05|D04
193|economic_facility.bank_default|返済滞納で店舗破壊部隊が襲来する。|P21|D04
196|guest.credit_exceeded|ツケ払いが信用度を超えると破産する。|P15|D04
196|guest.debt_settlement_imprisonment|女性破産客の原本イベントで即座にユニット拘束と投獄へ移る。|P19|D08
214|ui.hud_gold|上部に資金を表示する。|P24
214|ui.hud_mana|上部にマナを表示する。|P24
214|ui.hud_materials|上部に資材を表示する。|P24
214|ui.hud_time|上部に時間を表示する。|P24
214|ui.hud_weather|上部に天候を表示する。|P24
214|ui.hud_rank|上部に世界樹ランクを表示する。|P24
215|ui.bottom_layer_toggle|下部にFRONT/BACKの大きなレイヤー切替を表示する。|P24
215|ui.bottom_build_menu|下部に建設メニューを表示する。|P24
215|ui.bottom_management_menu|下部に管理メニューを表示する。|P24
216|ui.mouse_scroll|断面マップをマウスでスクロールできる。|P24
216|ui.zoom|断面マップをズームイン/アウトできる。|P24
216|ui.layer_emphasis|断面マップで選択中の表/裏層を視覚的に強調する。|P23
218|ui.facility_policy|施設詳細からポリシーを設定できる。|P24
218|ui.shift_drag_drop|施設のシフトへドラッグ&ドロップで人物を配置する。|P24
219|ui.guest_need_bars|客の欲求をバーで表示する。|P24
219|ui.guest_ai_log|客のAI思考ログを表示する。|P24
219|ui.staff_emotion_graph|従業員の原本3感情を3軸グラフで表示する。|P24|D07
219|ui.shield_durability|従業員の愛の盾耐久値を表示する。|P24
221|ui.observation_camera_entry|裏層のカメラアイコンから専用観察画面へ移る。|P24
222|ui.observation_angle|専用観察画面でアングルを変更できる。|P24
222|ui.observation_capture|専用観察画面でスクリーンショットによる根拠収集ができる。|P24
222|ui.observation_intervention|専用観察画面で介入操作ができる。|P24
224|ui.grid_snap|建設時にグリッドスナップする。|P24
224|ui.connection_warning|建設時に接続ラインの警告を表示する。|P24
225|ui.bubble_sleep|ユニット頭上にZzzのフキダシを表示する。|P23
225|ui.bubble_anger|ユニット頭上に怒りのフキダシを表示する。|P23
225|ui.bubble_heart|ユニット頭上にハートのフキダシを表示する。|P23
225|ui.event_log|イベントログを表示する。|P24
226|ui.warning_route_invasion|原本の侵食開始に重要警告演出を表示する。|P24
226|ui.cut_duel|決闘発生時にカットイン演出を表示する。|P24
226|ui.cut_defense|防衛開始時にカットイン演出を表示する。|P24
229|art.front_theme|表の自然・秩序・光を表現する。|P25|D09
229|art.back_theme|裏の人工・混沌・闇を表現する。|P25|D09
230|art.pixel_map|マップを高精細ドット絵で表現する。|P25|D09
230|art.pixel_sd|SDキャラを高精細ドット絵で表現する。|P25|D09
230|art.anime_portrait|イベント・会話の立ち絵を高品質アニメ塗りで表現する。|P25|D09
232|art.layer_ripple|レイヤー切替を魔法レンズ風の波紋としてシームレスに演出する。|P25|D09
232|audio.no_mechanical_switch|レイヤー切替に原本で否定された機械的な音を用いない。|P26
234|asset.sd_breathing|SDキャラの呼吸モーションを用意する。|P25|D09
234|asset.sd_secondary_motion|原本指定のSD身体動作差分を完成素材として組み込む。|P25|D09
234,245|asset.sd_job_walk|職種別の歩行モーションを用意する。|P25|D09
234,245|asset.sd_adult_loop|原本指定の成人向けループ完成素材をゲームに組み込み、制作・受入状態を中立台帳で追跡する。|P25|D09
235|asset.portrait_damage|立ち絵の破損差分を用意する。|P25|D09
235|asset.portrait_dirt|立ち絵の汚れ差分を用意する。|P25|D09
235|asset.portrait_love_tone|純愛ルートの立ち絵をソフトフォーカスとして区別する。|P25|D09
235|asset.portrait_other_tone|他ルートの指定陰影と表情の完成差分をゲームへ組み込み、中立素材台帳で区別する。|P25|D09
237,250|asset.ui_front_skin|表の羊皮紙・ファンタジーUIスキンを用意する。|P25|D09
237,250|asset.ui_back_skin|裏の黒鉄・モニター風UIスキンを用意する。|P25|D09
237|ui.dynamic_skin|レイヤーモードに合わせUIスキンを動的に変更する。|P24
238|audio.same_theme_arrangements|表裏のBGMは同曲のアレンジ違いとする。|P26|D09
238|audio.back_bass|裏BGMの重低音ベースを強調する。|P26|D09
238|audio.crossfade|表裏のBGMをクロスフェード再生する。|P26
239|audio.front_birds|表の環境音に小鳥を用意する。|P26|D09
239|audio.back_steam|裏の環境音に蒸気管を用意する。|P26|D09
239,252|asset.audio_adult|原本指定の成人向けSE完成素材をゲームに組み込み、制作・受入状態を中立台帳で追跡する。|P26|D09
242|asset.main_portraits|メインキャラの立ち絵を用意する。|P25|D09
242|asset.sub_portraits|サブキャラの立ち絵を用意する。|P25|D09
242|asset.portrait_expression|立ち絵の表情差分を用意する。|P25|D09
242|asset.portrait_outfit|立ち絵の衣装差分を用意する。|P25|D09
243|asset.route_event_cg|原本のルート別イベントCG完成素材をゲームに組み込み、制作・受入状態を中立台帳で追跡する。|P25|D09
243|asset.event_cg_variants|原本のイベントCG完成差分をゲームに組み込み、制作・受入状態を中立台帳で追跡する。|P25|D09
243|asset.mob_stills|原本のモブ汎用スチル完成素材をゲームに組み込み、制作・受入状態を中立台帳で追跡する。|P25|D09
245|asset.sd_idle|SDキャラの待機モーションを用意する。|P25|D09
245|asset.sd_work|SDキャラの作業モーションを用意する。|P25|D09
245|asset.sd_collapse|SDキャラの気絶モーションを用意する。|P25|D09
246|asset.effect_shield|愛の盾エフェクトを用意する。|P25|D09
246|asset.effect_smoke|煙エフェクトを用意する。|P25|D09
246|asset.effect_mana|マナの光エフェクトを用意する。|P25|D09
248|asset.distant_background|マップ背景遠景を用意する。|P25|D09
250|asset.ui_emotion_graph|感情グラフUI素材を用意する。|P25|D09
250|asset.ui_observation_overlay|専用観察画面に原本指定のカメラ風オーバーレイを用意する。|P25|D09
252|asset.dynamic_bgm|動的変化対応のBGM素材を用意する。|P26|D09
252|asset.environment_audio|環境音素材を用意する。|P26|D09
252|asset.ui_audio|UI操作音素材を用意する。|P26|D09
252|asset.character_voice|キャラクターボイス素材を用意する。|P26|D09
'''
for row in SPEC.strip().splitlines():
parts=row.split('|'); nums,key,condition,package=parts[:4]
group_ids=None
if nums=='59' or key=='time.speeds':group_ids=['B-3.1a']
if key in ['time.pause_build','time.pause_commands']:group_ids=['B-3.1b']
if key in ['economy.midnight_report','economy.settlement_calculation']:group_ids=['B-3.4a']
if key=='progression.three_deficits_gameover':group_ids=['B-3.4b']
if key in ['traffic.astar','traffic.elevator_wait_cost']:group_ids=['B-4.3a']
if key.startswith('traffic.') and nums=='86':group_ids=['B-4.3b']
if key=='traffic.overlap_half_speed':group_ids=['B-4.3c']
add(key,condition,[int(n) for n in nums.split(',')],package,group_ids,pending=parts[4] if len(parts)>4 else None)
# Each enumerated job is a separate obligation; role aliases do not add employees.
for num, jobs,layer in [(105,[('waiter','ウェイター'),('receptionist','受付係'),('bunny','バニー')],'表層'),
(106,[('chef','シェフ'),('cleaner','清掃員'),('carrier','運搬員'),('rescue','救護班')],'裏層'),
(107,[('adult_service','原本指定の地下接客職'),('security','警備員'),('trainer','原本指定の調教職')],'原本指定層')]:
for key,name in jobs:add('job.'+key,f'{name}の職種と{layer}での業務を通常配置から運用する。',[num],'P16',pending='D04')
# Original 7 characters are canonical identities, independent of current resource IDs.
heroes=[('anri','アンリ','女将',118,[('floor_price','配置フロアの全施設売上を20%増やす。',None)]),
('fine','フィーネ','メイド',119,[('cleaning_speed','清掃速度を3倍にする。',None),('assassination','暗殺確率を上げる。','D04')]),
('tomari','トマリ','騎士',120,[('floor_trouble','配置フロアのトラブルを0%にする。',None),('defense_score','防衛スコアを加算する。','D04')]),
('amelia','アメリア','シスター',121,[('donation','寄付金を3倍にする。',None),('recovery','回復速度を2倍にする。',None)]),
('liz','リズ','狩人',122,[('rare_material','レア素材発見率を上げる。','D04'),('food_saving','高級食材消費を減らす。','D04')]),
('mimi','ミミ','商人',123,[('purchase','仕入れ価格を20%減らす。',None),('sale','売却価格を20%増やす。',None)]),
('satsuki','サツキ','雑用',124,[('vip_satisfaction','VIP客の満足度上昇を1.5倍にする。',None),('tip','チップを増やす。','D04')])]
for key,name,role,num,effects in heroes:
ent={'kind':'heroine','key':key,'original_name':name}
add(f'heroine.{key}.identity',f'{name}を原本7人物の一人として通常進行で利用できる。',[30,num],'P17',entity=ent,pending='D08')
add(f'heroine.{key}.role',f'{name}は{role}として業務する。',[num],'P17',entity=ent)
for effect,condition,pending in effects:
add(f'heroine.{key}.{effect}',name+'の固有効果: '+condition,[num],'P17',entity=ent,pending=pending)
for route,label in [('love','純愛'),('ntr','NTR'),('corruption','堕落')]:
add(f'heroine.{key}.route.{route}',f'{name}の{label}ルートを通常進行で到達・記録できる。',[9,30,33,138],'P19',entity=ent,pending='D08')
for key,name in [('rai','ライ'),('riesenbart','リーゼンバルト'),('sou','ソウ')]:
add(f'rival.{key}.hiring',f'ライバル{name}を雇用できる。',[125],'P17',entity={'kind':'rival','key':key,'original_name':name},pending='D08')
add(f'rival.{key}.risk',f'ライバル{name}の雇用で原本の侵食リスクが生じる。',[125],'P18',pending='D04')
# Chapter 10 enumerates exactly 30 original facilities. Meaning keys are audit keys,
# NOT a claim that those IDs/resources are present in production.
FACS=[
('standard_room','一般客室',199,'2x1','FRONT',None,'P10',[167,46]),
('premium_room','上級客室',199,'4x1','FRONT',None,'P10',[167,46]),
('vip_suite','VIPスイート',199,'6x2','FRONT','高層','P10',[167,46]),
('dormitory','ドミトリー',199,'4x1','FRONT','地下','P10',[]),
('cafeteria','大衆食堂',201,'4x2','FRONT',None,'P11',[168,46]),
('restaurant','レストラン',201,'6x2','FRONT',None,'P11',[168,46]),
('tavern','酒場',201,'4x2','FRONT',None,'P11',[168,46]),
('food_stall','屋台',201,'2x1','FRONT',None,'P11',[]),
('public_bath','大浴場',203,'6x2','FRONT',None,'P11',[169]),
('casino','カジノ',203,'6x3','FRONT','地下','P11',[169]),
('arena','闘技場',203,'8x4','FRONT','地下','P11',[169]),
('chapel','礼拝堂',203,'4x3','FRONT',None,'P11',[169]),
('adult_private_room','風俗個室',205,'2x1','FRONT','地下','P13',[170]),
('luxury_soap','高級ソープ',205,'4x2','FRONT','地下','P13',[170]),
('sm_room','SM拷問室',205,'4x2','FRONT','地下','P13',[170]),
('black_market','闇市場',205,'4x2','FRONT','地下','P13',[170]),
('kitchen','厨房',207,'4x2','BACK',None,'P12',[173]),
('warehouse','倉庫',207,'4x2','BACK',None,'P12',[49,173]),
('staff_waiting_room','待機室',207,'4x2','BACK',None,'P12',[49,172]),
('staff_dormitory','従業員寮',207,'2x1','BACK',None,'P12',[49,172]),
('security_station','警備員詰所',207,'2x2','BACK',None,'P12',[172]),
('cleaning_room','清掃員室',207,'2x1','BACK',None,'P12',[173]),
('infirmary','医務室',207,'4x2','BACK',None,'P12',[173]),
('peeping_room','覗き部屋',209,'2x1','BACK',None,'P13',[49,174]),
('prison','地下牢',209,'4x2','BACK','地下','P13',[174]),
('mana_furnace','マナ炉',209,'6x3','BACK','地下','P13',[174]),
('guest_elevator','蔓の昇降機',211,'2x2','FRONT',None,'P14',[46,86,176]),
('service_elevator','業務用EV',211,'3x2','BACK',None,'P14',[49,86,176]),
('mana_warp','ワープ',211,'2x2','FRONT',None,'P14',[86,176]),
('magic_turret','魔法砲台',211,None,None,'外壁','P21',[])]
SUPP_FIELDS=[('construction_gold',1,'建設金額',None),('upkeep_gold',2,'運用金額',None),
('upkeep_mana',2,'運用マナ量','D04'),('service_price',3,'利用料金','D04'),
('footprint',4,'占有サイズ','D06'),('role',5,'役割','D04'),('operation_state',6,'稼働状態',None),
('customer_capacity',7,'同時利用顧客人数上限','D02'),('staff_min',8,'必要最低職員人数','D02'),('staff_max',8,'配置可能職員上限','D02'),
('related_facility',9,'関連施設指定','D03'),('related_operating',9,'関連施設の実稼働フラグ',None),
('service_attribute',10,'提供サービス属性','D01')]
for key,name,num,size,layer,zone,package,extra in FACS:
ent={'kind':'original_facility','key':key,'original_name':name}
anchors=[num]+extra
add(f'facility.{key}.exists',f'原本施設{name}を通常建設・運用できる。',anchors,package,entity=ent)
if size:add(f'facility.{key}.footprint',f'{name}の占有寸法は{size}。',[num],package,entity=ent,pending='D06')
if layer:add(f'facility.{key}.layer',f'{name}の層は{layer}。',[num],package,entity=ent)
if zone:add(f'facility.{key}.zone',f'{name}の配置条件は{zone}。',[num],package,entity=ent,pending='D04' if zone=='高層' else None)
for field,no,label,pending in SUPP_FIELDS:
rid='AR.'+f'facility.{key}.{field}'
cond=records[rid]['condition'] if rid in records else f'{name}の{label}を明示値または明示未設定として保持し、必要確定値は営業受入までに固定する。'
rec=add(f'facility.{key}.{field}',cond,[983+no],'P09',source=SUPPLEMENT,
authority='retained_record_of_explicit_user_facility_amendment',supplement=f'F{no:02}',entity=ent,pending=pending)
if field=='operation_state':rec['acceptance']['condition_assertion']=f'{name}のINACTIVE/OPERATING/SUSPENDEDを区別し、画面・Runtime・保存で一致する。'
if field=='customer_capacity':rec['acceptance']['condition_assertion']=f'{name}の利用中人数で専用上限を判定する。待機者とcapacityを流用しない。未設定を確定済み上限として受け入れない。'
if field=='staff_max':rec['acceptance']['condition_assertion']=f'{name}の職員上限を最低人数と分離し、上限超過の配置を原状保持で拒否する。'
if field=='related_operating':rec['acceptance']['condition_assertion']=f'{name}の関連先について配置有無ではなく実稼働を判定する。'
# Unspecified extension values and required operating values have different contracts.
for field,no,label,pending in SUPP_FIELDS:
rec=records[f'AR.facility.{key}.{field}']
rec['value_contract']={'field':field,'known_value':None,'certainty':'not_reaudited',
'null_or_unset_allowed_as_final':field=='related_facility',
'unset_policy':'単一関連先なしは表現できる。複数関連先/AND/ORは承認なしに追加しない。' if field=='related_facility' else '未設定は台帳で可視化するが必要営業値の確定・受入の代用にしない。',
'source_has_numeric_value':False}
if field=='footprint' and size:rec['value_contract'].update(known_value=size,certainty='original_explicit',source_has_numeric_value=True)
if field=='operation_state':rec['value_contract'].update(known_value=['INACTIVE','OPERATING','SUSPENDED'],certainty='explicit_amendment_enum')
# Finished visual slots are finite explicitly specified combinations (30 x 3 x 2 x 2).
for level in [1,2,3]:
for view in ['front','back']:
for part,label in [('exterior','外観'),('interior','内装')]:
add(f'asset.facility.{key}.lv{level}.{view}.{part}',f'{name} Lv{level} {view} の{label}完成素材を用意する。',[248],'P25',entity=ent,pending='D09')
# Other original infrastructure remains outside the chapter-10 count, never silently dropped.
for key,name,nums in [('guest_corridor','客用通路',[46,177]),('service_corridor','裏通路',[49,177]),('stairs','階段',[86,176])]:
add(f'infrastructure.{key}.exists',f'{name}を通常建設し移動へ接続する。',nums,'P14',pending='D04')
for key in ['cafeteria','restaurant','tavern']:
add(f'facility.{key}.kitchen_dependency',f'{dict((f[0],f[1]) for f in FACS)[key]}は裏層の厨房とセットで稼働する。',[168],'P11')
add('facility.kitchen.behind_food','厨房は表層飲食店の裏に置く。',[173],'P12')
add('facility.vip_suite.high_beauty','VIPスイートは高景観を必要とする。',[167],'P10',pending='D04')
for key,nums in [('vip_suite',[167,199]),('public_bath',[169,203]),('adult_private_room',[170,205])]:
add(f'facility.{key}.observation','対象施設は原本指定の観察機能に対応する。',nums,'P13')
add('facility.tavern.noise','酒場が騒音を発生する。',[201],'P22',pending='D04')
add('facility.staff_dormitory.night_event','従業員寮に原本指定の夜間イベント対応がある。',[172],'P19',pending='D08')
add('facility.mana_furnace.unit_consumption','マナ炉で原本指定のユニット消費が発生する。',[209],'P13',pending='D04')
# Global semantic contracts for the ten fields. These are distinct from individual configured values.
CONTRACTS=[
('construction.payment',1,'通常建設で配置コストを支払う。','P08',None),
('facility.state_three_values',6,'稼働状態はINACTIVE/OPERATING/SUSPENDEDの3状態として扱う。','P09',None),
('facility.destroy_suspend',6,'破壊された施設を休止する。','P09',None),
('facility.enforcement_suspend',6,'摘発された施設を休止する。','P09',None),
('facility.no_auto_resume',6,'関連施設の条件回復だけで破壊・摘発後の休止を解除しない。','P09','D03'),
('facility.customer_not_capacity',7,'customer_capacityを保管量・生産量等のcapacityから推定しない。','P09','D02'),
('facility.customer_wait_separate',7,'待機者と利用中顧客数を分ける。','P09',None),
('facility.staff_min_separate',8,'職員最低人数と職員上限を別に保持する。','P09','D02'),
('facility.related_single',9,'関連先は単一指定の現契約を保持する。','P09','D03'),
('facility.related_not_placement',9,'関連施設の配置有無を実稼働判定に置き換えない。','P09',None),
('pricing.attributes',10,'サービス属性の既存4型(appetite/relaxation/sexual/gambling)を区別する。','P09','D01'),
('pricing.match_surcharge',3,'欲求一致時だけ指定した加算を料金へ適用する。','P09','D01'),
('pricing.mismatch_allowed',10,'欲求不一致を施設利用不可の理由にしない。','P09',None),
('pricing.no_unset_invention',10,'不足基準・加算額/率・複数属性・重複加算の未指定を勝手に補完しない。','P09','D01'),
('facility.no_resume_invention',6,'指定されていない休止復帰条件を追加しない。','P09','D03'),
('facility.no_multiple_related_invention',9,'指定されていない複数関連施設とAND/ORを追加しない。','P09','D03')]
for key,no,cond,package,pending in CONTRACTS:
add(key,cond,[983+no],package,source=SUPPLEMENT,authority='retained_record_of_explicit_user_facility_amendment',supplement=f'F{no:02}',pending=pending)
# Approved A01-A05 refine existing atoms; none create a second weight for the same value.
DEC_LINE={'A01':6,'A02':13,'A03':20,'A04':27,'A05':33}
def approved(key,cond,decision,package='P09',replace=False,pending=None):
r=add(key,cond,[DEC_LINE[decision]],package,source=DECISIONS,
authority='explicit_user_approved_amendment',decision=decision,replace=replace,pending=pending)
if replace:
r['value_contract']={'known_value_statement':cond,'certainty':'approved_initial_trial_value','decision_id':decision,'null_or_unset_allowed_as_final':False}
r['pending_decision_ids']=[x for x in r['pending_decision_ids'] if x not in ['D01','D02','D04']]
r['decision_dependencies']=[x for x in r['decision_dependencies'] if x['decision_id'] in r['pending_decision_ids']]
return r
for key,cond in [
('upkeep.start_completion','維持費は施設完成時から計上する。'),
('upkeep.end_removal','維持費は撤去まで計上する。'),
('upkeep.time_proration','維持費日額を保有時間で按分する。'),
('upkeep.closed_accrual','閉店中も維持費を計上する。'),
('upkeep.suspended_accrual','休止中も維持費を計上する。'),
('upkeep.midnight_payment','維持費を00:00に精算する。'),
('upkeep.shortfall_carry','維持費不足額を翌日へ繰り越す。')]: approved(key,cond,'A01','P05')
for key,cond in [
('upkeep.trial_rate','一般施設の維持費初期試験値は建築費の0.5%/日。'),
('upkeep.integer_arithmetic','維持費は既存の整数処理を保持する。'),
('upkeep.special_override','礼拝堂・清掃員室の維持費に個別承認値を優先する。')]:approved(key,cond,'A02','P05')
approved('heroine.anri.recruit_entry','従業員画面でアンリの無料加入を選ぶ。','A03','P17')
approved('heroine.anri.floor_price','配置フロアのアンリ補正で全施設の利用料金を20%増加する。','A03','P17',replace=True)
approved('heroine.anri.wallet_revenue','アンリ補正後の客支払額と施設収入を一致させる。','A03','P05')
for decision,key,vals in [
('A04','chapel',{'footprint':'占有寸法4x3','layer':'表層','construction_gold':'建築1200G','construction_materials':'資材12','construction_minutes':'工期180分','customer_capacity':'同時利用8人','staff_min':'聖職者最低1人','staff_max':'聖職者上限2人','service_minutes':'礼拝60分','service_price':'寄付20G','completion_faith':'礼拝完了時信仰+1','upkeep_gold':'維持費6G/日'}),
('A05','cleaning_room',{'footprint':'占有寸法2x1','layer':'裏層','construction_gold':'建築350G','construction_materials':'資材4','construction_minutes':'工期60分','staff_min':'清掃員最低1人','staff_max':'清掃員上限2人','customer_capacity':'顧客0人','service_price':'利用料なし','upkeep_gold':'維持費2G/日'})]:
for field,val in vals.items():approved(f'facility.{key}.{field}',f'{key}の承認初期試験値は{val}。',decision,replace=True)
for key,cond in [
('cleaning.cleanliness_range','清潔度の範囲は0〜100。'),
('cleaning.initial_cleanliness','清潔度初期値は100。'),
('cleaning.reservation_threshold','清潔度75以下で清掃予約する。'),
('cleaning.on_site_minutes','現地清掃所要時間は8分。'),
('cleaning.recovery_amount','現地清掃完了で清潔度を25回復する。'),
('cleaning.defined_dirt_only','定義済み汚れ量を使用する。'),
('cleaning.no_undefined_dirt','未定義の汚れ量を勝手に補完しない。')]:approved(key,cond,'A05','P16')
# Historical milestones: content requirements remain mapped in their feature domains;
# these atoms test the dated delivery promise and cannot be retrospectively passed.
SCHEDULE=[(255,'B-14.1','2026/03〜08',[('layers','表裏レイヤー切替'),('ai','AI移動ロジック'),('assets','基本素材'),('mvp','MVP')]),
(256,'B-14.2','2026/09〜2027/02',[('parameters','パラメータ管理'),('portraits','立ち絵量産'),('tiles','施設タイル量産'),('scenario','シナリオ組込み'),('bgm','通常BGM組込み')]),
(257,'B-14.3','2027/03〜08',[('adult','原本指定の成人向け内容'),('observation','専用観察モード'),('voice_record','ボイス収録'),('voice_integrate','ボイス組込み'),('balance','ゲームバランス徹底調整')]),
(258,'B-14.4','2027/09〜12',[('debug','進行不能バグ等のデバッグ'),('ui','UIレスポンス向上'),('mastering','マスタリング'),('final','最終完成')])]
for num,gid,window,deliverables in SCHEDULE:
for key,label in deliverables:
r=add(f'schedule.{gid}.{key}',f'{window}の工程内に{label}を達成する。',[num],'P00',[gid],scope='historical_schedule')
r['acceptance']['evidence_policy']='日付付き当時の成果が必要。後日の機能実装で過去納期達成をPASSにしない。'
r['historical_due_window']=window
ADDITIONAL_APPLICABILITY_EXPANSION = {'decision_id': 'D09', 'scope': 'additional_named_facilities_and_infrastructure_applicability', 'reason': '原本第10章30施設以外にも有限の名指し施設/設備がある。通常機能条件は存在するが、追加F01〜F10とLv別表裏/外内素材の全適用範囲は未確定。30施設の展開から外れていることを最終全施設網羅として受入しない。適用範囲確定時に原子ID追加/統合と分母改訂の履歴を記録する。一律12素材枠や全項目適用を推測で追加しない。', 'source_examples': [{'lines': [190], 'names': ['受付/精算所']}, {'lines': [191], 'names': ['金庫室']}, {'lines': [192], 'names': ['購買部']}, {'lines': [193], 'names': ['闇銀行']}, {'lines': [49, 177], 'names': ['裏通路']}, {'lines': [46, 177], 'names': ['客用通路']}, {'lines': [50, 177], 'names': ['従業員ドア']}, {'lines': [86, 176], 'names': ['階段']}, {'lines': [49], 'names': ['リネンシュート', '隠し扉']}], 'applicability_decisions_required': ['facility_supplement_F01_to_F10', 'levels_1_to_3', 'front_back_views', 'exterior_interior_visuals']}
# Align primary responsibility with the reviewed plan's actual package titles.
for rec in records.values():
rid=rec['id']; primary=rec['primary_package']
if primary=='P22':primary='P07' # weather/environment implementation; P22 is the asset inventory
if primary=='P07':primary='P23' if rid.startswith(('AR.ui.','AR.world.')) else 'P07'
if rid.startswith(('AR.weather.','AR.environment.','AR.news.')):primary='P06' if rid=='AR.environment.ground_mana' else 'P07'
if rid in ['AR.world.unit_grid','AR.world.above_ground','AR.world.below_ground','AR.world.ground_boundary']:primary='P08'
if rid=='AR.world.dual_management':primary='P20'
if rid.startswith(('AR.duel.','AR.route_action.')):primary='P19'
if rid.startswith('AR.audio.'):primary='P25'
if rid=='AR.art.layer_ripple':primary='P23'
if rid.startswith('AR.asset.portrait_') or rid in ['AR.asset.main_portraits','AR.asset.sub_portraits','AR.asset.route_event_cg','AR.asset.event_cg_variants','AR.asset.mob_stills','AR.history.pregnancy_appearance']:primary='P26'
if rid in ['AR.asset.dynamic_bgm','AR.asset.environment_audio','AR.asset.ui_audio','AR.asset.audio_adult']:primary='P25'
if rid=='AR.asset.character_voice':primary='P26'
if rid.startswith(('AR.facility.cafeteria.','AR.facility.restaurant.','AR.facility.tavern.','AR.facility.food_stall.')) and primary=='P11':primary='P10'
if rid=='AR.facility.tavern.noise':primary='P07'
rec['primary_package']=rec['package']=primary
rec['normal_entry']=normal_entries.get(primary,'要件別通常入口の設計と検証')
# Group-level routing is kept for cross-cutting work, separately from primary ownership.
inherited={p for g in coverage_groups if g['requirement_id'] in rec['source_group_ids'] for p in g['work_packages']}
rec['work_packages']=sorted(inherited|{primary})
# All group mappings use condition anchors. Headings are structural aliases, not extra counts.
line_map={n:[] for n in range(1,len(lines)+1)}
for r in records.values():
for a in r['source_anchors']:
if a['path']==BASE:
for n in a['lines']:line_map[n].append(r['id'])
coverage=[]
for n,line in enumerate(lines,1):
ids=sorted(set(line_map[n])); kind='condition'
if line.startswith('#'):
kind='structural_heading'
level=len(line)-len(line.lstrip('#'))
end=n
for j in range(n+1,len(lines)+1):
if lines[j-1].startswith('#') and len(lines[j-1])-len(lines[j-1].lstrip('#'))<=level:break
end=j
ids=sorted({rid for j in range(n+1,end+1) for rid in line_map[j]})
elif n==7:
kind='structural_label';ids=sorted(set(line_map[8]+line_map[9]))
elif n==18:
kind='structural_label';ids=sorted(set(line_map[19]+line_map[20]))
elif n==150:
kind='structural_description';ids=sorted(set(line_map[151]+line_map[152]+line_map[153]))
elif n==254:
kind='schedule_window';ids=sorted({rid for j in range(255,259) for rid in line_map[j]})
coverage.append({'line':n,'kind':kind,'line_sha256':hashlib.sha256(line.encode()).hexdigest(),'atomic_ids':ids})
feature_ids=sorted(r['id'] for r in records.values() if r['scope']=='feature_or_content')
schedule_ids=sorted(r['id'] for r in records.values() if r['scope']=='historical_schedule')
register={'schema_version':1,'register_version':'atomic-v1-2026-10-02','generated_at_jst':now,
'production_root':str(ROOT),'source_head':head,'source_pins':pins,
'authority_policy':'指定原本保存版 > 明示承認/追加仕様(重複条件を精緻化) > 別冊参照 > 派生文書。今回新規Drive取得なし。',
'completion_views':VIEWS,'state_policy':'未再監査はunverified/not_reaudited。PASSと適用外は証拠と独立判定者が必要。',
'id_policy':'AR.<domain>.<canonical-source-entity>.<condition>; canonical-source-entity is an audit identity, not a production resource id',
'denominator_version':'atomic-v1-2026-10-02','denominator_frozen':True,
'denominator_scope':'Known-condition register v1 only. Final denominator is NOT established while D08/D09 target catalogs remain unresolved. Saved-original conditions plus retained explicit additions and A01-A05; no invented content targets or unapproved values.',
'final_denominator_established':False, 'final_catalog_coverage_claimed':False,
'denominator_ids':feature_ids,'historical_schedule_ids':schedule_ids,'duplicate_merges':merges,
'open_expansion_register':[{'decision_id':'D08','reason':'全エンド/図鑑対象/章/制作内容の個別IDは未指定。集合契約と各人物3ルートを保持。確定時に原子ID追加・統合を分母改訂履歴へ記録し、集合契約の合格を個別内容の代用にしない。'},
{'decision_id':'D09','reason':'サブ人物数・表情/衣装/CG/音の全差分量は未指定。有限明示される施設30×Lv1〜3×表裏×外内のみ個別化済み。確定数量の独立条件は分母改訂が必要。'}],
'requirements':sorted(records.values(),key=lambda r:r['id'])}
register['open_expansion_register'].append(ADDITIONAL_APPLICABILITY_EXPANSION)
source_coverage={'schema_version':1,'source_path':BASE,'source_sha256':pins[BASE],
'line_count':len(lines),'group_count':len(groups),'source_lines':coverage,
'groups':[{'group_id':g['id'],'source_lines':g['source_lines'],
'atomic_ids':sorted(r['id'] for r in records.values() if g['id'] in r['source_group_ids']),
'scope':'historical_schedule' if g['id'].startswith('B-14.') else 'feature_or_content'} for g in groups],
'facility_ids':[{'key':f[0],'original_name':f[1], 'original_line':f[2],
'atomic_ids':sorted(r['id'] for r in records.values() if r['entity'] and r['entity'].get('kind')=='original_facility' and r['entity']['key']==f[0])} for f in FACS],
'heroine_ids':[{'key':h[0],'original_name':h[1], 'atomic_ids':sorted(r['id'] for r in records.values() if r['entity'] and r['entity'].get('kind')=='heroine' and r['entity']['key']==h[0])} for h in heroes],
'approved_decisions':[{'decision_id':d, 'atomic_ids':sorted(r['id'] for r in records.values() if d in r['user_amendment_refs'])} for d in DEC_LINE],
'facility_supplements':[{'supplement_id':f'F{n:02}', 'atomic_ids':sorted(r['id'] for r in records.values() if f'F{n:02}' in r['supplement_ids'])} for n in range(1,11)]}
freeze={'schema_version':1,'denominator_version':register['denominator_version'],'source_head':head,
'source_pins':pins,'calculated_at_jst':now,'denominator_frozen':True,'counting_unit':'one unique independently judgeable source condition',
'freeze_scope':'known-condition v1 only; final content-target denominator remains unresolved',
'final_denominator_established':False,'denominator':len(feature_ids),'denominator_ids':feature_ids,'historical_schedule_count':len(schedule_ids),'historical_schedule_ids':schedule_ids,
'implemented_ids':[],'accepted_ids':[],'blocked_ids':[], 'partial_ids':[], 'missing_evidence_ids':feature_ids,
'difference_ids':[],'difference_audit_state':'not_reaudited',
'not_reaudited_ids':feature_ids,'pending_decision_ids':sorted({d for r in records.values() for d in r['pending_decision_ids']}),
'implementation_percent':0.0,'acceptance_percent':0.0,
'percent_label':'currently evidenced whole-atom completion; 0 does not mean no legacy code exists',
'applicability_review_pending_ids':feature_ids,'independent_reviewer':None,
'change_control':{'requires':['previous_version','new_version','added_ids','removed_ids','merged_ids','reason','reviewer','source_reference'],
'history':[{'previous_version':None,'new_version':register['denominator_version'],'added_ids':feature_ids,
'removed_ids':[],'merged_ids':merges,'reason':'initial source split; no game acceptance inferred','reviewer':None,
'review_state':'independent_review_required'}]}}
for name,data in [('atomic_requirements.json',register),('source_atomic_coverage.json',source_coverage),('atomic_denominator_freeze.json',freeze)]:
(OUT/name).write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n')
print(json.dumps({'feature_atoms':len(feature_ids),'historical_schedule_atoms':len(schedule_ids),
'source_lines':len(coverage),'unmapped_lines':[r['line'] for r in coverage if not r['atomic_ids']],
'unmapped_groups':[r['group_id'] for r in source_coverage['groups'] if not r['atomic_ids']],
'merges':len(merges)},ensure_ascii=False))