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.
735 lines
64 KiB
Python
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))
|