Add character location presence data
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
- `data/characters.json`: 22명의 성인 여성 캐릭터와 각자의 루트 구조
|
- `data/characters.json`: 22명의 성인 여성 캐릭터와 각자의 루트 구조
|
||||||
- `data/locations.json`: 맵 클릭 지점, 주요 시간대, 우연한 만남 태그
|
- `data/locations.json`: 맵 클릭 지점, 주요 시간대, 우연한 만남 태그
|
||||||
|
- `data/character_location_presence.json`: 캐릭터별 출현 가능 장소, 시간대, 등장 가중치
|
||||||
- `data/progression.json`: 만남에서 연인 전환까지의 공통 진행 규칙
|
- `data/progression.json`: 만남에서 연인 전환까지의 공통 진행 규칙
|
||||||
- `data/character_assets.json`: 캐릭터 기본 이미지의 통과 상태와 파일 경로
|
- `data/character_assets.json`: 캐릭터 기본 이미지의 통과 상태와 파일 경로
|
||||||
- `docs/game_structure.md`: 게임 루프와 시스템 설계 메모
|
- `docs/game_structure.md`: 게임 루프와 시스템 설계 메모
|
||||||
|
|||||||
1008
data/character_location_presence.json
Normal file
1008
data/character_location_presence.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -26,16 +26,21 @@ def main() -> None:
|
|||||||
characters_doc = load_json(DATA / "characters.json")
|
characters_doc = load_json(DATA / "characters.json")
|
||||||
locations_doc = load_json(DATA / "locations.json")
|
locations_doc = load_json(DATA / "locations.json")
|
||||||
progression_doc = load_json(DATA / "progression.json")
|
progression_doc = load_json(DATA / "progression.json")
|
||||||
|
presence_path = DATA / "character_location_presence.json"
|
||||||
|
presence_doc = load_json(presence_path) if presence_path.exists() else None
|
||||||
|
|
||||||
minimum_age = characters_doc["content_rules"]["minimum_age"]
|
minimum_age = characters_doc["content_rules"]["minimum_age"]
|
||||||
locations = {location["id"] for location in locations_doc["locations"]}
|
location_docs = {location["id"]: location for location in locations_doc["locations"]}
|
||||||
|
locations = set(location_docs)
|
||||||
character_ids: set[str] = set()
|
character_ids: set[str] = set()
|
||||||
|
preferred_locations_by_character: dict[str, set[str]] = {}
|
||||||
|
|
||||||
for character in characters_doc["characters"]:
|
for character in characters_doc["characters"]:
|
||||||
character_id = character["id"]
|
character_id = character["id"]
|
||||||
if character_id in character_ids:
|
if character_id in character_ids:
|
||||||
fail(f"duplicate character id: {character_id}")
|
fail(f"duplicate character id: {character_id}")
|
||||||
character_ids.add(character_id)
|
character_ids.add(character_id)
|
||||||
|
preferred_locations_by_character[character_id] = set(character["preferred_locations"])
|
||||||
|
|
||||||
if character["age"] < minimum_age:
|
if character["age"] < minimum_age:
|
||||||
fail(
|
fail(
|
||||||
@@ -72,6 +77,72 @@ def main() -> None:
|
|||||||
if adult_requirements["minimum_character_age"] != minimum_age:
|
if adult_requirements["minimum_character_age"] != minimum_age:
|
||||||
fail("progression minimum age does not match character content rules")
|
fail("progression minimum age does not match character content rules")
|
||||||
|
|
||||||
|
if presence_doc is not None:
|
||||||
|
presence_ids: set[str] = set()
|
||||||
|
valid_availability = set(presence_doc["availability_values"])
|
||||||
|
valid_roles = set(presence_doc["role_values"])
|
||||||
|
|
||||||
|
for entry in presence_doc["characters"]:
|
||||||
|
character_id = entry["character_id"]
|
||||||
|
if character_id not in character_ids:
|
||||||
|
fail(f"presence references missing character id: {character_id}")
|
||||||
|
if character_id in presence_ids:
|
||||||
|
fail(f"duplicate presence entry for character id: {character_id}")
|
||||||
|
presence_ids.add(character_id)
|
||||||
|
|
||||||
|
seen_locations: set[str] = set()
|
||||||
|
total_weight = 0
|
||||||
|
for candidate in entry["locations"]:
|
||||||
|
location_id = candidate["location_id"]
|
||||||
|
if location_id not in locations:
|
||||||
|
fail(f"{character_id} presence references missing location {location_id}")
|
||||||
|
if location_id in seen_locations:
|
||||||
|
fail(f"{character_id} has duplicate presence location {location_id}")
|
||||||
|
seen_locations.add(location_id)
|
||||||
|
|
||||||
|
role = candidate["role"]
|
||||||
|
if role not in valid_roles:
|
||||||
|
fail(f"{character_id} has invalid presence role {role}")
|
||||||
|
|
||||||
|
availability = candidate["availability"]
|
||||||
|
if availability not in valid_availability:
|
||||||
|
fail(f"{character_id} has invalid availability {availability}")
|
||||||
|
|
||||||
|
weight = candidate["weight"]
|
||||||
|
if not isinstance(weight, int) or weight <= 0:
|
||||||
|
fail(f"{character_id} has invalid presence weight {weight}")
|
||||||
|
total_weight += weight
|
||||||
|
|
||||||
|
location_time_slots = set(location_docs[location_id]["time_slots"])
|
||||||
|
for time_slot in candidate["time_slots"]:
|
||||||
|
if time_slot not in location_time_slots:
|
||||||
|
fail(
|
||||||
|
f"{character_id} uses invalid time slot {time_slot} "
|
||||||
|
f"for location {location_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not candidate["hook"].strip():
|
||||||
|
fail(f"{character_id} presence location {location_id} lacks hook text")
|
||||||
|
|
||||||
|
if total_weight != 100:
|
||||||
|
fail(f"{character_id} presence weights must sum to 100, got {total_weight}")
|
||||||
|
|
||||||
|
missing_preferred = preferred_locations_by_character[character_id].difference(
|
||||||
|
seen_locations
|
||||||
|
)
|
||||||
|
if missing_preferred:
|
||||||
|
fail(
|
||||||
|
f"{character_id} presence lacks preferred locations: "
|
||||||
|
f"{sorted(missing_preferred)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if presence_ids != character_ids:
|
||||||
|
fail(
|
||||||
|
"presence character mismatch; "
|
||||||
|
f"missing={sorted(character_ids.difference(presence_ids))}, "
|
||||||
|
f"extra={sorted(presence_ids.difference(character_ids))}"
|
||||||
|
)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
"Validated "
|
"Validated "
|
||||||
f"{len(character_ids)} characters, "
|
f"{len(character_ids)} characters, "
|
||||||
|
|||||||
Reference in New Issue
Block a user