174 lines
6.3 KiB
Python
174 lines
6.3 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DOCS_DIR = ROOT / "docs"
|
|
MANIFEST_PATH = DOCS_DIR / "unit-sprite-rework-manifest.json"
|
|
AUDIT_PATH = DOCS_DIR / "unit-sprite-rework-audit.json"
|
|
DEFAULT_OUTPUT = DOCS_DIR / "unit-sprite-next-steps.md"
|
|
SPEC_BY_STEM = {
|
|
"unit-shu-spearman": "unit-sprite-batch2-shu-spearman-spec.md",
|
|
"unit-shu-archer": "unit-sprite-batch2-shu-archer-spec.md",
|
|
"unit-shu-cavalry": "unit-sprite-batch2-shu-cavalry-spec.md",
|
|
"unit-shu-strategist": "unit-sprite-batch2-shu-strategist-spec.md",
|
|
"unit-shu-officer": "unit-sprite-batch2-shu-officer-spec.md",
|
|
}
|
|
|
|
|
|
def load_json(path: Path) -> dict:
|
|
if not path.exists():
|
|
raise FileNotFoundError(f"missing required file: {path.relative_to(ROOT)}")
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def row_by_stem(audit: dict) -> dict[str, dict]:
|
|
return {row["stem"]: row for row in audit.get("rows", [])}
|
|
|
|
|
|
def command_block(commands: list[str]) -> list[str]:
|
|
return ["```powershell", *commands, "```"]
|
|
|
|
|
|
def write_report(output: Path) -> None:
|
|
manifest = load_json(MANIFEST_PATH)
|
|
audit = load_json(AUDIT_PATH)
|
|
rows = row_by_stem(audit)
|
|
approved = manifest.get("approvedBaseline", [])
|
|
pending = manifest.get("pendingApproval", [])
|
|
candidates = manifest.get("currentApprovalCandidate", {})
|
|
next_after = manifest.get("nextAfterApproval", [])
|
|
stage_counts = audit.get("stage_counts", {})
|
|
|
|
lines = [
|
|
"# Unit Sprite Next Steps",
|
|
"",
|
|
"This file is generated by `scripts/write-unit-sprite-next-steps.py`.",
|
|
"",
|
|
"## Current Gate",
|
|
"",
|
|
f"- Approved baseline units: {len(approved)}",
|
|
f"- Pending approval units: {len(pending)}",
|
|
f"- Queued rework units: {stage_counts.get('queued-rework', 0)}",
|
|
f"- Manifest: `{MANIFEST_PATH.relative_to(ROOT).as_posix()}`",
|
|
f"- Audit: `{AUDIT_PATH.relative_to(ROOT).as_posix()}`",
|
|
"- Batch inventory boards include old queued assets; use them for inventory/regression checks, not approval.",
|
|
"",
|
|
]
|
|
if pending:
|
|
lines.insert(-2, "- Current approval board: `docs/unit-sprite-current-approval-board.png`")
|
|
else:
|
|
lines.insert(-2, "- Current approval board: not generated until the next pending sample.")
|
|
|
|
if pending:
|
|
lines.extend(
|
|
[
|
|
"## Approval Blocker",
|
|
"",
|
|
"Do not expand to the next unit, commit, push, or deploy while these units remain pending:",
|
|
"",
|
|
]
|
|
)
|
|
for stem in pending:
|
|
row = rows.get(stem)
|
|
if row:
|
|
base = row["base"]
|
|
lines.append(
|
|
f"- `{stem}`: {row['review_stage']}, base `{base['status']}`, "
|
|
f"battle bbox `{base.get('battle_min_width_px')}..{base.get('battle_max_width_px')} x "
|
|
f"{base.get('battle_min_height_px')}..{base.get('battle_max_height_px')}`"
|
|
)
|
|
else:
|
|
lines.append(f"- `{stem}`: missing from audit")
|
|
lines.append("")
|
|
|
|
promote_commands = []
|
|
if pending:
|
|
unit = pending[0]
|
|
version = candidates.get(unit, {}).get("version") if isinstance(candidates, dict) else None
|
|
command = f"python scripts\\promote-unit-sprite-approval.py --unit {unit}"
|
|
if version:
|
|
command += f" --expected-version {version}"
|
|
promote_commands.append(command)
|
|
|
|
lines.extend(
|
|
[
|
|
"## Next Unit Queue",
|
|
"",
|
|
]
|
|
)
|
|
if next_after:
|
|
for index, stem in enumerate(next_after, start=1):
|
|
row = rows.get(stem)
|
|
status = "missing from audit"
|
|
if row:
|
|
status = f"{row['review_stage']}, base `{row['base']['status']}`, actions `{row['actions']['status']}`"
|
|
spec = SPEC_BY_STEM.get(stem)
|
|
spec_note = f", spec `{spec}`" if spec else ""
|
|
lines.append(f"{index}. `{stem}` - {status}{spec_note}")
|
|
else:
|
|
lines.append("No queued next unit is defined in the manifest.")
|
|
|
|
lines.extend(
|
|
[
|
|
"",
|
|
"## Commands Before Approval",
|
|
"",
|
|
*command_block(
|
|
[
|
|
"python scripts\\audit-unit-sprite-sheets.py",
|
|
"python scripts\\render-unit-sprite-review-boards.py",
|
|
"python scripts\\render-current-sprite-approval-board.py",
|
|
"python scripts\\check-unit-sprite-art-quality.py",
|
|
"python scripts\\check-unit-sprite-approval-gate.py --mode sample",
|
|
"python scripts\\write-unit-sprite-next-steps.py",
|
|
"python scripts\\check-unit-sprite-workflow-consistency.py",
|
|
]
|
|
),
|
|
"",
|
|
"## Commands After Approval",
|
|
"",
|
|
"After user approval, promote the approved unit in `unit-sprite-rework-manifest.json`, regenerate outputs, and only then use the release gate.",
|
|
"",
|
|
*command_block(
|
|
[
|
|
*promote_commands,
|
|
"python scripts\\audit-unit-sprite-sheets.py",
|
|
"python scripts\\render-unit-sprite-review-boards.py",
|
|
"python scripts\\check-unit-sprite-approval-gate.py --mode release",
|
|
"pnpm build",
|
|
]
|
|
),
|
|
"",
|
|
"## First Task After Current Approval",
|
|
"",
|
|
]
|
|
)
|
|
|
|
if next_after:
|
|
first = next_after[0]
|
|
spec = SPEC_BY_STEM.get(first, "")
|
|
lines.append(f"- Start with `{first}`.")
|
|
if spec:
|
|
lines.append(f"- Use spec: `{spec}`.")
|
|
lines.append("- Generate source art, assemble sheets, produce contact/GIF/before-after/scale/battle screenshots, then rerun the gates.")
|
|
else:
|
|
lines.append("- No first task is defined.")
|
|
|
|
output.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Write the next-step report for sprite rework.")
|
|
parser.add_argument("--out", type=Path, default=DEFAULT_OUTPUT)
|
|
args = parser.parse_args()
|
|
write_report(args.out)
|
|
print(f"Wrote {args.out}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|