diff --git a/Docs/Art/AgrarianAssetPipeline.md b/Docs/Art/AgrarianAssetPipeline.md index 3bef72e..820f9b3 100644 --- a/Docs/Art/AgrarianAssetPipeline.md +++ b/Docs/Art/AgrarianAssetPipeline.md @@ -22,7 +22,8 @@ not look cartoonish, old-west, or exaggerated apocalypse junkyard. ## Approved Sources -- Fab free assets or assets explicitly owned for this project. +- Fab free assets. Paid Fab assets are blocked unless Nathan explicitly approves + the purchase in a later task. - Quixel/Megascans assets available under the current Epic/Unreal terms. - CC0/public-domain art libraries. - Assets created internally. @@ -31,6 +32,16 @@ not look cartoonish, old-west, or exaggerated apocalypse junkyard. Do not scrape random internet images or models. If an asset cannot be traced to a usable license, reject it. +## Free-Only Lockdown + +The asset pipeline is currently free-only. Before download or import, verify +that the asset page is marked free or that the asset is project-owned/internal. +Record the cost in `Docs/Art/AssetLicenses.md` as `Free`, `$0`, `0`, or `N/A`. + +Do not click purchase, checkout, add payment, subscription, or paid-license +flows. If an asset looks useful but is not free, record it as a candidate in +notes outside the import flow and wait for explicit approval. + ## Staging Workflow 1. Place downloaded/manual assets in: diff --git a/Docs/Art/AssetLicenses.md b/Docs/Art/AssetLicenses.md index 159ba9b..033794d 100644 --- a/Docs/Art/AssetLicenses.md +++ b/Docs/Art/AssetLicenses.md @@ -5,8 +5,8 @@ before it is used in a playable map, packaged demo, screenshot, or trailer. Allowed default sources: -- Fab assets explicitly marked free or otherwise purchased/owned for this - project. +- Fab assets explicitly marked free. Paid Fab assets are blocked unless Nathan + explicitly approves a purchase in a later task. - Quixel/Megascans assets available under the current Epic/Unreal terms for this project. - CC0 or public-domain assets. @@ -30,6 +30,16 @@ Expected subfolders: - `Processed`: assets imported, optimized, renamed, and verified. - `Rejected`: assets that should not be used. +## Free-Only Acquisition Gate + +Until Nathan explicitly approves a paid purchase, every third-party asset must +have `Cost` recorded as `Free`, `$0`, `0`, or `N/A` for project-owned/internal +assets. Do not use "purchased", "paid", a dollar amount above zero, or blank +cost values in the register. + +Any asset with uncertain cost, marketplace bundle requirements, subscription +requirements, or unclear entitlement belongs in `Rejected` until reviewed. + ## Naming Policy Use project-readable names before import: diff --git a/Scripts/verify_asset_license_free_only.py b/Scripts/verify_asset_license_free_only.py new file mode 100644 index 0000000..b4327a6 --- /dev/null +++ b/Scripts/verify_asset_license_free_only.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Fail if tracked Agrarian assets include paid or unclear costs.""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +REGISTER = ROOT / "Docs" / "Art" / "AssetLicenses.md" + +ALLOWED_COSTS = {"free", "$0", "0", "n/a", "na"} +BLOCKED_WORDS = { + "paid", + "purchased", + "purchase", + "subscription", + "unknown", + "unclear", + "tbd", + "marketplace bundle", +} + + +def parse_markdown_table_rows(text: str) -> list[list[str]]: + rows: list[list[str]] = [] + for line in text.splitlines(): + stripped = line.strip() + if not stripped.startswith("|") or "---" in stripped: + continue + cells = [cell.strip() for cell in stripped.strip("|").split("|")] + if cells and cells[0] != "Asset": + rows.append(cells) + return rows + + +def main() -> None: + if not REGISTER.exists(): + raise SystemExit(f"Missing asset license register: {REGISTER}") + + rows = parse_markdown_table_rows(REGISTER.read_text(encoding="utf-8")) + failures: list[str] = [] + + for row in rows: + if len(row) < 5: + failures.append(f"Malformed asset license row: {' | '.join(row)}") + continue + + asset = row[0] + cost = row[4].strip().lower() + full_row = " ".join(row).lower() + + if not cost: + failures.append(f"{asset}: cost is blank") + elif cost not in ALLOWED_COSTS: + failures.append(f"{asset}: cost {row[4]!r} is not free-only") + + for blocked in BLOCKED_WORDS: + if blocked in full_row: + failures.append(f"{asset}: blocked free-only word found: {blocked!r}") + + if failures: + raise SystemExit("\n".join(failures)) + + print(f"Asset license free-only verification complete: {len(rows)} entries.") + + +if __name__ == "__main__": + main() diff --git a/Scripts/verify_asset_pipeline_policy.py b/Scripts/verify_asset_pipeline_policy.py index ee38bf5..5c5a631 100644 --- a/Scripts/verify_asset_pipeline_policy.py +++ b/Scripts/verify_asset_pipeline_policy.py @@ -12,12 +12,16 @@ REQUIRED = { "CC0 or public-domain", "/home/nathan/AssetStaging/Agrarian", "Do not import random internet images", + "Free-Only Acquisition Gate", + "Paid Fab assets are blocked", "Native Ground Zero proxy vegetation", ], "Docs/Art/AgrarianAssetPipeline.md": [ "realistic modern post-collapse frontier survival", "Old abandoned equipment", "Do not scrape random internet images or models", + "Free-Only Lockdown", + "Do not click purchase", "LicenseEvidence", "Run visual and placeholder verifiers", "Water should be handled as a shader/system problem",