Add sensual location background assets
@@ -11,6 +11,7 @@
|
||||
- `data/character_location_presence.json`: 캐릭터별 출현 가능 장소, 시간대, 등장 가중치
|
||||
- `data/progression.json`: 만남에서 연인 전환까지의 공통 진행 규칙
|
||||
- `data/character_assets.json`: 캐릭터 기본 이미지의 통과 상태와 파일 경로
|
||||
- `data/location_assets.json`: 장소별 배경 CG의 통과 상태와 파일 경로
|
||||
- `docs/game_structure.md`: 게임 루프와 시스템 설계 메모
|
||||
- `docs/art/facial_identity_matrix.md`: 캐릭터별 얼굴형, 눈매, 헤어, 표정 차별화 기준
|
||||
- `scripts/validate_content.py`: 캐릭터 나이와 장소 참조 무결성 검증
|
||||
@@ -26,6 +27,7 @@
|
||||
|
||||
- 박민서 피트니스 데이트 101+ CG는 `assets/characters/date_cg/park_minseo_fitness_affection_100plus_v2.png`를 사용합니다.
|
||||
- 신규 후보 10장과 비교용 contact sheet는 `assets/characters/date_cg/candidates/park_minseo_100plus/`에 보관합니다.
|
||||
- 장소 배경 CG 22장은 `assets/locations/`에 보관하며, 비교용 contact sheet는 `assets/locations/location_backgrounds_contact_sheet.png`입니다.
|
||||
|
||||
## 검증
|
||||
|
||||
|
||||
BIN
assets/locations/amusement_street_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
assets/locations/apartment_complex_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
assets/locations/art_studio_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
assets/locations/book_cafe_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/locations/boutique_studio_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
assets/locations/cinema_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
assets/locations/cooking_studio_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
assets/locations/downtown_cafe_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
assets/locations/fashion_street_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
assets/locations/fitness_center_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
assets/locations/flower_shop_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
assets/locations/global_lounge_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
assets/locations/jazz_bar_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/locations/lakeside_walk_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
assets/locations/location_backgrounds_contact_sheet.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
assets/locations/neighborhood_market_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
assets/locations/office_district_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
assets/locations/old_bookstore_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
assets/locations/police_box_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
assets/locations/riverside_park_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
assets/locations/rooftop_lounge_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
assets/locations/university_quarter_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
assets/locations/wine_bar_bg_v1.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
146
data/location_assets.json
Normal file
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"style": {
|
||||
"id": "sensual_romance_backgrounds",
|
||||
"description": "Glossy adult romance visual-novel location backgrounds with mature lighting, saturated reflections, and no characters."
|
||||
},
|
||||
"minimum_dimensions": {
|
||||
"width": 1280,
|
||||
"height": 720
|
||||
},
|
||||
"contact_sheet": "assets/locations/location_backgrounds_contact_sheet.png",
|
||||
"assets": [
|
||||
{
|
||||
"location_id": "downtown_cafe",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/downtown_cafe_bg_v1.png",
|
||||
"notes": "Rainy downtown cafe interior with amber light and intimate two-person seating."
|
||||
},
|
||||
{
|
||||
"location_id": "book_cafe",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/book_cafe_bg_v1.png",
|
||||
"notes": "Warm private reading lounge with velvet seating and late-afternoon light."
|
||||
},
|
||||
{
|
||||
"location_id": "university_quarter",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/university_quarter_bg_v1.png",
|
||||
"notes": "Adult campus-adjacent nightlife street with neon, food stalls, and wet pavement."
|
||||
},
|
||||
{
|
||||
"location_id": "riverside_park",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/riverside_park_bg_v1.png",
|
||||
"notes": "Blue-hour riverside path with skyline reflections and confession-route calm."
|
||||
},
|
||||
{
|
||||
"location_id": "cooking_studio",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/cooking_studio_bg_v1.png",
|
||||
"notes": "Upscale cooking studio after class with warm counters, produce, and oven glow."
|
||||
},
|
||||
{
|
||||
"location_id": "neighborhood_market",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/neighborhood_market_bg_v1.png",
|
||||
"notes": "Early-morning market lane with saturated produce, lamps, ice cases, and puddles."
|
||||
},
|
||||
{
|
||||
"location_id": "office_district",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/office_district_bg_v1.png",
|
||||
"notes": "Rainy glass-tower office district with after-work tension and lobby glow."
|
||||
},
|
||||
{
|
||||
"location_id": "rooftop_lounge",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/rooftop_lounge_bg_v1.png",
|
||||
"notes": "Luxury rooftop lounge with velvet seating, city lights, and red-gold highlights."
|
||||
},
|
||||
{
|
||||
"location_id": "wine_bar",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/wine_bar_bg_v1.png",
|
||||
"notes": "Private wine bar with burgundy leather, bottle walls, and glass sparkle."
|
||||
},
|
||||
{
|
||||
"location_id": "jazz_bar",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/jazz_bar_bg_v1.png",
|
||||
"notes": "After-hours jazz bar with empty stage, piano, spotlights, and velvet shadows."
|
||||
},
|
||||
{
|
||||
"location_id": "amusement_street",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/amusement_street_bg_v1.png",
|
||||
"notes": "Neon amusement street with arcade glow, reflections, and adult date-night energy."
|
||||
},
|
||||
{
|
||||
"location_id": "fashion_street",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/fashion_street_bg_v1.png",
|
||||
"notes": "Rainy fashion street with boutique windows, mannequins, and glossy pavement."
|
||||
},
|
||||
{
|
||||
"location_id": "boutique_studio",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/boutique_studio_bg_v1.png",
|
||||
"notes": "High-end fashion studio with mirrors, vanity bulbs, satin curtains, and camera gear."
|
||||
},
|
||||
{
|
||||
"location_id": "fitness_center",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/fitness_center_bg_v1.png",
|
||||
"notes": "Late-night premium gym with red rim lighting, mirrors, and glossy equipment."
|
||||
},
|
||||
{
|
||||
"location_id": "art_studio",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/art_studio_bg_v1.png",
|
||||
"notes": "Rainy art-studio alley with warm doorway light, canvases, and emotional mood."
|
||||
},
|
||||
{
|
||||
"location_id": "old_bookstore",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/old_bookstore_bg_v1.png",
|
||||
"notes": "Narrow old bookstore with amber lamps, aged paper, and hidden-corner intimacy."
|
||||
},
|
||||
{
|
||||
"location_id": "flower_shop",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/flower_shop_bg_v1.png",
|
||||
"notes": "Closing-time flower shop with deep roses, warm sunlight, and lush floral color."
|
||||
},
|
||||
{
|
||||
"location_id": "cinema",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/cinema_bg_v1.png",
|
||||
"notes": "Empty premium cinema with red panels, plush seats, and late-night date tension."
|
||||
},
|
||||
{
|
||||
"location_id": "police_box",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/police_box_bg_v1.png",
|
||||
"notes": "Rainy police-box exterior with warm safety light and restrained late-night mood."
|
||||
},
|
||||
{
|
||||
"location_id": "global_lounge",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/global_lounge_bg_v1.png",
|
||||
"notes": "International lounge with city-view windows, brass lamps, and cosmopolitan warmth."
|
||||
},
|
||||
{
|
||||
"location_id": "apartment_complex",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/apartment_complex_bg_v1.png",
|
||||
"notes": "Modern apartment courtyard at night with rain reflections and private comfort."
|
||||
},
|
||||
{
|
||||
"location_id": "lakeside_walk",
|
||||
"status": "accepted",
|
||||
"path": "assets/locations/lakeside_walk_bg_v1.png",
|
||||
"notes": "Quiet lakeside boardwalk with lanterns, mist, and mature confession mood."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -57,10 +57,12 @@ def validate_asset_image(path: Path, min_width: int, min_height: int, label: str
|
||||
|
||||
def main() -> None:
|
||||
characters_doc = load_json(DATA / "characters.json")
|
||||
locations_doc = load_json(DATA / "locations.json")
|
||||
assets_doc = load_json(DATA / "character_assets.json")
|
||||
|
||||
character_ids = [character["id"] for character in characters_doc["characters"]]
|
||||
character_id_set = set(character_ids)
|
||||
location_ids = {location["id"] for location in locations_doc["locations"]}
|
||||
asset_entries = assets_doc["assets"]
|
||||
asset_ids = [entry["character_id"] for entry in asset_entries]
|
||||
|
||||
@@ -158,12 +160,64 @@ def main() -> None:
|
||||
f"{cg_set['set_id']} revision candidate {affection_min}",
|
||||
)
|
||||
|
||||
location_asset_path = DATA / "location_assets.json"
|
||||
location_accepted = 0
|
||||
location_revision_count = 0
|
||||
if location_asset_path.exists():
|
||||
location_assets_doc = load_json(location_asset_path)
|
||||
location_minimum = location_assets_doc["minimum_dimensions"]
|
||||
location_min_width = location_minimum["width"]
|
||||
location_min_height = location_minimum["height"]
|
||||
|
||||
contact_sheet = ROOT / location_assets_doc["contact_sheet"]
|
||||
if not contact_sheet.exists():
|
||||
fail(f"location contact sheet is missing: {contact_sheet}")
|
||||
png_dimensions(contact_sheet)
|
||||
|
||||
location_asset_entries = location_assets_doc["assets"]
|
||||
asset_location_ids = [entry["location_id"] for entry in location_asset_entries]
|
||||
if set(asset_location_ids) != location_ids:
|
||||
missing = sorted(location_ids.difference(asset_location_ids))
|
||||
extra = sorted(set(asset_location_ids).difference(location_ids))
|
||||
fail(f"location asset manifest mismatch; missing={missing}, extra={extra}")
|
||||
|
||||
if len(asset_location_ids) != len(set(asset_location_ids)):
|
||||
fail("location asset manifest contains duplicate location ids")
|
||||
|
||||
for entry in location_asset_entries:
|
||||
status = entry["status"]
|
||||
if status not in {"accepted", "needs_revision", "pending", "rejected"}:
|
||||
fail(f"{entry['location_id']} has invalid location asset status {status}")
|
||||
|
||||
path_value = entry.get("path")
|
||||
if status == "accepted":
|
||||
location_accepted += 1
|
||||
if not path_value:
|
||||
fail(f"{entry['location_id']} accepted location asset lacks a path")
|
||||
validate_asset_image(
|
||||
ROOT / path_value,
|
||||
location_min_width,
|
||||
location_min_height,
|
||||
entry["location_id"],
|
||||
)
|
||||
elif status == "needs_revision":
|
||||
location_revision_count += 1
|
||||
if path_value:
|
||||
validate_asset_image(
|
||||
ROOT / path_value,
|
||||
location_min_width,
|
||||
location_min_height,
|
||||
f"{entry['location_id']} revision candidate",
|
||||
)
|
||||
|
||||
print(
|
||||
"Validated character asset manifest: "
|
||||
f"{accepted}/{len(asset_entries)} accepted, "
|
||||
f"{revision_count} needs revision, "
|
||||
f"{date_cg_accepted} accepted date CG tiers, "
|
||||
f"{date_cg_revision_count} date CG tiers need revision."
|
||||
f"{date_cg_revision_count} date CG tiers need revision, "
|
||||
f"{location_accepted} accepted location backgrounds, "
|
||||
f"{location_revision_count} location backgrounds need revision."
|
||||
)
|
||||
|
||||
|
||||
|
||||