#!/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...; 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))