diff --git a/README.md b/README.md index 46f2bd0..124c3b5 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,9 @@ python3 scripts/validate_content.py python3 scripts/validate_assets.py ``` + +## Character Location CG Assets + +- Anchor character-location CG round 01 is stored under `assets/characters/location_cg//`. +- The round 01 comparison sheet is `assets/characters/location_cg/anchor_locations_round01_contact_sheet.png`. +- The manifest is `data/character_location_assets.json`; `scripts/validate_assets.py` checks character/location references, presence availability, PNG files, and minimum 1280x720 dimensions. diff --git a/assets/characters/location_cg/anchor_locations_round01_contact_sheet.png b/assets/characters/location_cg/anchor_locations_round01_contact_sheet.png new file mode 100644 index 0000000..290ddfc Binary files /dev/null and b/assets/characters/location_cg/anchor_locations_round01_contact_sheet.png differ diff --git a/assets/characters/location_cg/baek_chaewon/baek_chaewon_rooftop_lounge_v1.png b/assets/characters/location_cg/baek_chaewon/baek_chaewon_rooftop_lounge_v1.png new file mode 100644 index 0000000..d823034 Binary files /dev/null and b/assets/characters/location_cg/baek_chaewon/baek_chaewon_rooftop_lounge_v1.png differ diff --git a/assets/characters/location_cg/cha_yuri/cha_yuri_fashion_street_v1.png b/assets/characters/location_cg/cha_yuri/cha_yuri_fashion_street_v1.png new file mode 100644 index 0000000..121212a Binary files /dev/null and b/assets/characters/location_cg/cha_yuri/cha_yuri_fashion_street_v1.png differ diff --git a/assets/characters/location_cg/choi_ria/choi_ria_boutique_studio_v1.png b/assets/characters/location_cg/choi_ria/choi_ria_boutique_studio_v1.png new file mode 100644 index 0000000..b0f073b Binary files /dev/null and b/assets/characters/location_cg/choi_ria/choi_ria_boutique_studio_v1.png differ diff --git a/assets/characters/location_cg/go_eunjae/go_eunjae_office_district_v1.png b/assets/characters/location_cg/go_eunjae/go_eunjae_office_district_v1.png new file mode 100644 index 0000000..74d1e42 Binary files /dev/null and b/assets/characters/location_cg/go_eunjae/go_eunjae_office_district_v1.png differ diff --git a/assets/characters/location_cg/han_seoyun/han_seoyun_cooking_studio_v1.png b/assets/characters/location_cg/han_seoyun/han_seoyun_cooking_studio_v1.png new file mode 100644 index 0000000..9151206 Binary files /dev/null and b/assets/characters/location_cg/han_seoyun/han_seoyun_cooking_studio_v1.png differ diff --git a/assets/characters/location_cg/hong_sua/hong_sua_jazz_bar_v1.png b/assets/characters/location_cg/hong_sua/hong_sua_jazz_bar_v1.png new file mode 100644 index 0000000..9f80e25 Binary files /dev/null and b/assets/characters/location_cg/hong_sua/hong_sua_jazz_bar_v1.png differ diff --git a/assets/characters/location_cg/jung_naeun/jung_naeun_downtown_cafe_v1.png b/assets/characters/location_cg/jung_naeun/jung_naeun_downtown_cafe_v1.png new file mode 100644 index 0000000..4dc210a Binary files /dev/null and b/assets/characters/location_cg/jung_naeun/jung_naeun_downtown_cafe_v1.png differ diff --git a/assets/characters/location_cg/kang_mirae/kang_mirae_office_district_v1.png b/assets/characters/location_cg/kang_mirae/kang_mirae_office_district_v1.png new file mode 100644 index 0000000..35b5de7 Binary files /dev/null and b/assets/characters/location_cg/kang_mirae/kang_mirae_office_district_v1.png differ diff --git a/assets/characters/location_cg/kim_doa/kim_doa_university_quarter_v1.png b/assets/characters/location_cg/kim_doa/kim_doa_university_quarter_v1.png new file mode 100644 index 0000000..bf4ff17 Binary files /dev/null and b/assets/characters/location_cg/kim_doa/kim_doa_university_quarter_v1.png differ diff --git a/assets/characters/location_cg/lee_bom/lee_bom_flower_shop_v1.png b/assets/characters/location_cg/lee_bom/lee_bom_flower_shop_v1.png new file mode 100644 index 0000000..2ae5d5f Binary files /dev/null and b/assets/characters/location_cg/lee_bom/lee_bom_flower_shop_v1.png differ diff --git a/assets/characters/location_cg/lim_sohee/lim_sohee_downtown_cafe_v1.png b/assets/characters/location_cg/lim_sohee/lim_sohee_downtown_cafe_v1.png new file mode 100644 index 0000000..fb29af6 Binary files /dev/null and b/assets/characters/location_cg/lim_sohee/lim_sohee_downtown_cafe_v1.png differ diff --git a/assets/characters/location_cg/marina/marina_global_lounge_v1.png b/assets/characters/location_cg/marina/marina_global_lounge_v1.png new file mode 100644 index 0000000..7c2c603 Binary files /dev/null and b/assets/characters/location_cg/marina/marina_global_lounge_v1.png differ diff --git a/assets/characters/location_cg/moon_jia/moon_jia_amusement_street_v1.png b/assets/characters/location_cg/moon_jia/moon_jia_amusement_street_v1.png new file mode 100644 index 0000000..16ea8a8 Binary files /dev/null and b/assets/characters/location_cg/moon_jia/moon_jia_amusement_street_v1.png differ diff --git a/assets/characters/location_cg/nam_juhee/nam_juhee_police_box_v1.png b/assets/characters/location_cg/nam_juhee/nam_juhee_police_box_v1.png new file mode 100644 index 0000000..b62100a Binary files /dev/null and b/assets/characters/location_cg/nam_juhee/nam_juhee_police_box_v1.png differ diff --git a/assets/characters/location_cg/oh_sera/oh_sera_flower_shop_v1.png b/assets/characters/location_cg/oh_sera/oh_sera_flower_shop_v1.png new file mode 100644 index 0000000..0154188 Binary files /dev/null and b/assets/characters/location_cg/oh_sera/oh_sera_flower_shop_v1.png differ diff --git a/assets/characters/location_cg/park_minseo/park_minseo_fitness_center_v1.png b/assets/characters/location_cg/park_minseo/park_minseo_fitness_center_v1.png new file mode 100644 index 0000000..7c35a4e Binary files /dev/null and b/assets/characters/location_cg/park_minseo/park_minseo_fitness_center_v1.png differ diff --git a/assets/characters/location_cg/ryu_haena/ryu_haena_fashion_street_v1.png b/assets/characters/location_cg/ryu_haena/ryu_haena_fashion_street_v1.png new file mode 100644 index 0000000..dbc6e88 Binary files /dev/null and b/assets/characters/location_cg/ryu_haena/ryu_haena_fashion_street_v1.png differ diff --git a/assets/characters/location_cg/seo_irin/seo_irin_art_studio_v1.png b/assets/characters/location_cg/seo_irin/seo_irin_art_studio_v1.png new file mode 100644 index 0000000..7c08f2b Binary files /dev/null and b/assets/characters/location_cg/seo_irin/seo_irin_art_studio_v1.png differ diff --git a/assets/characters/location_cg/shin_yerim/shin_yerim_wine_bar_v1.png b/assets/characters/location_cg/shin_yerim/shin_yerim_wine_bar_v1.png new file mode 100644 index 0000000..029e3ff Binary files /dev/null and b/assets/characters/location_cg/shin_yerim/shin_yerim_wine_bar_v1.png differ diff --git a/assets/characters/location_cg/song_arin/song_arin_apartment_complex_v1.png b/assets/characters/location_cg/song_arin/song_arin_apartment_complex_v1.png new file mode 100644 index 0000000..b20f487 Binary files /dev/null and b/assets/characters/location_cg/song_arin/song_arin_apartment_complex_v1.png differ diff --git a/assets/characters/location_cg/yoon_gayoung/yoon_gayoung_apartment_complex_v1.png b/assets/characters/location_cg/yoon_gayoung/yoon_gayoung_apartment_complex_v1.png new file mode 100644 index 0000000..b2ae750 Binary files /dev/null and b/assets/characters/location_cg/yoon_gayoung/yoon_gayoung_apartment_complex_v1.png differ diff --git a/assets/characters/location_cg/yu_harin/yu_harin_old_bookstore_v1.png b/assets/characters/location_cg/yu_harin/yu_harin_old_bookstore_v1.png new file mode 100644 index 0000000..20b9209 Binary files /dev/null and b/assets/characters/location_cg/yu_harin/yu_harin_old_bookstore_v1.png differ diff --git a/data/character_location_assets.json b/data/character_location_assets.json new file mode 100644 index 0000000..1c01097 --- /dev/null +++ b/data/character_location_assets.json @@ -0,0 +1,174 @@ +{ + "version": "0.1.0", + "description": "Accepted anchor-location encounter CGs for the first character-location art pass.", + "minimum_dimensions": { + "width": 1280, + "height": 720 + }, + "source_presence_manifest": "data/character_location_presence.json", + "contact_sheet": "assets/characters/location_cg/anchor_locations_round01_contact_sheet.png", + "generation_rounds": [ + { + "round_id": "anchor_locations_round01", + "description": "One mature glossy anime encounter CG for each character's anchor location.", + "status": "accepted", + "asset_count": 22 + } + ], + "assets": [ + { + "character_id": "han_seoyun", + "location_id": "cooking_studio", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/han_seoyun/han_seoyun_cooking_studio_v1.png" + }, + { + "character_id": "yu_harin", + "location_id": "old_bookstore", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/yu_harin/yu_harin_old_bookstore_v1.png" + }, + { + "character_id": "kang_mirae", + "location_id": "office_district", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/kang_mirae/kang_mirae_office_district_v1.png" + }, + { + "character_id": "jung_naeun", + "location_id": "downtown_cafe", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/jung_naeun/jung_naeun_downtown_cafe_v1.png" + }, + { + "character_id": "baek_chaewon", + "location_id": "rooftop_lounge", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/baek_chaewon/baek_chaewon_rooftop_lounge_v1.png" + }, + { + "character_id": "oh_sera", + "location_id": "flower_shop", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/oh_sera/oh_sera_flower_shop_v1.png" + }, + { + "character_id": "moon_jia", + "location_id": "amusement_street", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/moon_jia/moon_jia_amusement_street_v1.png" + }, + { + "character_id": "lim_sohee", + "location_id": "downtown_cafe", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/lim_sohee/lim_sohee_downtown_cafe_v1.png" + }, + { + "character_id": "cha_yuri", + "location_id": "fashion_street", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/cha_yuri/cha_yuri_fashion_street_v1.png" + }, + { + "character_id": "shin_yerim", + "location_id": "wine_bar", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/shin_yerim/shin_yerim_wine_bar_v1.png" + }, + { + "character_id": "kim_doa", + "location_id": "university_quarter", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/kim_doa/kim_doa_university_quarter_v1.png" + }, + { + "character_id": "park_minseo", + "location_id": "fitness_center", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/park_minseo/park_minseo_fitness_center_v1.png" + }, + { + "character_id": "seo_irin", + "location_id": "art_studio", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/seo_irin/seo_irin_art_studio_v1.png" + }, + { + "character_id": "yoon_gayoung", + "location_id": "apartment_complex", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/yoon_gayoung/yoon_gayoung_apartment_complex_v1.png" + }, + { + "character_id": "choi_ria", + "location_id": "boutique_studio", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/choi_ria/choi_ria_boutique_studio_v1.png" + }, + { + "character_id": "hong_sua", + "location_id": "jazz_bar", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/hong_sua/hong_sua_jazz_bar_v1.png" + }, + { + "character_id": "lee_bom", + "location_id": "flower_shop", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/lee_bom/lee_bom_flower_shop_v1.png" + }, + { + "character_id": "go_eunjae", + "location_id": "office_district", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/go_eunjae/go_eunjae_office_district_v1.png" + }, + { + "character_id": "ryu_haena", + "location_id": "fashion_street", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/ryu_haena/ryu_haena_fashion_street_v1.png" + }, + { + "character_id": "nam_juhee", + "location_id": "police_box", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/nam_juhee/nam_juhee_police_box_v1.png" + }, + { + "character_id": "marina", + "location_id": "global_lounge", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/marina/marina_global_lounge_v1.png" + }, + { + "character_id": "song_arin", + "location_id": "apartment_complex", + "round_id": "anchor_locations_round01", + "status": "accepted", + "path": "assets/characters/location_cg/song_arin/song_arin_apartment_complex_v1.png" + } + ] +} diff --git a/scripts/validate_assets.py b/scripts/validate_assets.py index 7546723..5144286 100644 --- a/scripts/validate_assets.py +++ b/scripts/validate_assets.py @@ -210,6 +210,88 @@ def main() -> None: f"{entry['location_id']} revision candidate", ) + character_location_asset_path = DATA / "character_location_assets.json" + character_location_accepted = 0 + character_location_revision_count = 0 + if character_location_asset_path.exists(): + character_location_assets_doc = load_json(character_location_asset_path) + character_location_minimum = character_location_assets_doc[ + "minimum_dimensions" + ] + character_location_min_width = character_location_minimum["width"] + character_location_min_height = character_location_minimum["height"] + + contact_sheet = ROOT / character_location_assets_doc["contact_sheet"] + if not contact_sheet.exists(): + fail(f"character-location contact sheet is missing: {contact_sheet}") + png_dimensions(contact_sheet) + + presence_doc = load_json(DATA / "character_location_presence.json") + allowed_pairs = { + (entry["character_id"], location["location_id"]) + for entry in presence_doc["characters"] + for location in entry["locations"] + } + + character_location_asset_entries = character_location_assets_doc["assets"] + seen_pairs = set() + for entry in character_location_asset_entries: + character_id = entry["character_id"] + location_id = entry["location_id"] + pair = (character_id, location_id) + + if character_id not in character_id_set: + fail( + "character-location asset references missing character id: " + f"{character_id}" + ) + if location_id not in location_ids: + fail( + "character-location asset references missing location id: " + f"{location_id}" + ) + if pair not in allowed_pairs: + fail( + "character-location asset references unavailable location: " + f"{character_id}/{location_id}" + ) + if pair in seen_pairs: + fail( + "character-location asset manifest contains duplicate pair: " + f"{character_id}/{location_id}" + ) + seen_pairs.add(pair) + + status = entry["status"] + if status not in {"accepted", "needs_revision", "pending", "rejected"}: + fail( + f"{character_id}/{location_id} has invalid " + f"character-location asset status {status}" + ) + + path_value = entry.get("path") + if status == "accepted": + character_location_accepted += 1 + if not path_value: + fail( + f"{character_id}/{location_id} accepted asset lacks a path" + ) + validate_asset_image( + ROOT / path_value, + character_location_min_width, + character_location_min_height, + f"{character_id}/{location_id}", + ) + elif status == "needs_revision": + character_location_revision_count += 1 + if path_value: + validate_asset_image( + ROOT / path_value, + character_location_min_width, + character_location_min_height, + f"{character_id}/{location_id} revision candidate", + ) + print( "Validated character asset manifest: " f"{accepted}/{len(asset_entries)} accepted, " @@ -217,7 +299,9 @@ def main() -> None: f"{date_cg_accepted} accepted date CG tiers, " f"{date_cg_revision_count} date CG tiers need revision, " f"{location_accepted} accepted location backgrounds, " - f"{location_revision_count} location backgrounds need revision." + f"{location_revision_count} location backgrounds need revision, " + f"{character_location_accepted} accepted character-location CGs, " + f"{character_location_revision_count} character-location CGs need revision." )