Audience: creative-tech leads shipping Unity UI built from SpriteAtlas PNG exports. Outcome: a remote Mac worker that turns “someone exported last night” into structured gates: width and height are powers of two when your GPU or CDN brief demands POT, alpha matches an RGBA versus RGB contract, and on-disk bytes stay under max_bytes per SKU. Mechanism: debounced directory watch plus bash and ImageMagick snippets that OpenClaw may invoke through a narrow Gateway—same scripts in git, not ad-hoc chat shell.
Table of Contents
Anchor the policy layer in the nine-grid and social slice matrix for Retina naming, pair transparency rules with the WebP → PNG alpha playbook, and mirror log discipline with watch, retry, and JSONL archive plus the Skills UI watchdog pattern.
Worked example
Suppose your project writes atlases to /Volumes/WorkNVMe/unity_jobs/ui_atlas/Exports/SpriteAtlas/ with stems such as hud_main_1024.png. Art expects POT 1024×1024, RGBA for overlays, and a hard 1.2 MB ceiling per file for a CDN tier. Unity completes each write as *.png.tmp then mv to *.png. The remote Mac runs fswatch, waits twelve seconds of silence, grabs /var/run/atlas-inspect.lock, runs the scan script, appends one JSON line per file, and moves any failure into quarantine/2026-04-23/ with a reason_code. That single paragraph is the contract you paste into README so engineers in Taipei and producers in Berlin argue about the same numbers.
| Check | Pass signal | Typical fail |
|---|---|---|
| POT | w & (w-1) == 0 and same for h when brief says POT |
Odd padding from tight packing off powers of two |
| Alpha | magick identify -format '%[channels]' contains rgba for translucent HUD |
Platform preset stripped alpha to rgb |
| Bytes | stat -f%z ≤ YAML max_bytes |
Unused transparent margin bloating PNG |
Directory listening strategy
Root choice: keep exports on APFS local NVMe, not an iCloud-backed Desktop, so file sizes stabilize before stat runs. Atomic finish: always write through a temp name; the watcher should ignore *.tmp and only scan stable *.png. Debounce: after the last close event, sleep a quiet window—eight to twenty seconds is common for Unity—then enqueue one job id. Single-flight: use flock or a lock file so two parallel builds cannot double-scan the same tree. OpenClaw: follow the install guide, bind the Gateway to loopback, and allowlist exactly ATLAS_EXPORT_ROOT, quarantine/, and logs/; reproduce TOKEN_FILE permissions inside the same launchd EnvironmentVariables you use in SSH smoke tests.
Trigger options: brew install fswatch piped into a small supervisor script, or launchd WatchPaths if the export path is fixed—document whichever you ship. Ignore .DS_Store, ignore zero-byte placeholders, and drop events for paths outside the allowlist so Gateway reviews stay boring.
Batch command templates
Install ImageMagick 7 so magick is on the worker PATH recorded in your plist. Export ATLAS_EXPORT_ROOT, LOG_DIR, MAX_BYTES, and REQUIRE_ALPHA before calling the script from OpenClaw or launchd.
POT helper and per-file probe (bash):
is_pot() { local n="$1"; [[ "$n" =~ ^[0-9]+$ ]] && (( (n > 0) && (n & (n-1)) == 0 )); }
magick identify -format '%w %h %[channels]' "$f"
bytes=$(stat -f%z "$f"); test "$bytes" -le "$MAX_BYTES"
Full loop skeleton (save as bin/atlas_scan.sh, chmod 750, call only from reviewed automation):
#!/usr/bin/env bash
set -euo pipefail
ROOT="${ATLAS_EXPORT_ROOT:?}"
MAX_BYTES="${MAX_BYTES:-1200000}"
REQUIRE_ALPHA="${REQUIRE_ALPHA:-1}"
LOG_DIR="${LOG_DIR:-./logs}"
mkdir -p "$LOG_DIR"
is_pot() { local n="$1"; [[ "$n" =~ ^[0-9]+$ ]] && (( (n > 0) && (n & (n-1)) == 0 )); }
while IFS= read -r -d '' f; do
[[ "$f" == *.png ]] || continue
read -r w h ch < <(magick identify -format '%w %h %[channels]' "$f")
bytes=$(stat -f%z "$f")
ok=1; reasons=()
is_pot "$w" || { ok=0; reasons+=("pot_w"); }
is_pot "$h" || { ok=0; reasons+=("pot_h"); }
if [[ "$REQUIRE_ALPHA" == "1" && "$ch" != *rgba* ]]; then ok=0; reasons+=("alpha"); fi
if (( bytes > MAX_BYTES )); then ok=0; reasons+=("size"); fi
jq -nc --arg f "$f" --argjson w "$w" --argjson h "$h" --arg ch "$ch" \
--argjson bytes "$bytes" --argjson ok "$ok" --arg r "$(IFS=,; echo "${reasons[*]}")" \
'{file:$f,w:$w,h:$h,channels:$ch,bytes:$bytes,ok:$ok,reasons:$r}' >> "$LOG_DIR/atlas-scan.jsonl"
if [[ "$ok" -ne 1 ]]; then
mkdir -p quarantine
base=$(basename "$f"); stamp=$(date +%Y%m%d%H%M%S); mv "$f" "quarantine/${stamp}_${base}"
fi
done < <(find "$ROOT" -type f -name '*.png' -print0)
If jq is unavailable, replace the jq line with a printf JSON fragment as in our Shortcuts batching article—just keep one JSON object per line for gzip friendly greps. After a pass, optionally run magick identify -define identify:limit=0 -verbose "$f" | head on quarantined samples to capture color-type hints for artists.
Failure retries and log archiving
Classify before retrying. Transient: file still locked, GPU helper still flushing, short read from a busy volume—retry with exponential backoff (for example two, four, eight seconds capped at sixty) and cap attempts at three per trace_id. Data: POT mismatch, wrong channels, real oversize—move to quarantine, attach reason_code, and require a human-updated Unity preset or manifest before requeue. Operational: missing magick, bad token path, disk watermark—pause the worker globally and page ops; do not spin retries that hammer logs.
JSONL hygiene: append trace_id, batch_id, host, and tool versions once per job header line if multiple workers exist. Rotate daily to logs/YYYY-MM-DD.jsonl, gzip files older than a few days into archive/YYYY-MM/, and keep the live tail small so rg '"ok":false' stays instant. Align rotation cadence with the watch / retry / archive HowTo so on-call runbooks match other PNG pipelines.
Troubleshooting FAQ
Atlas is POT and RGBA but still oversize—should the watcher auto-compress?
Not by default. Auto-pngquant changes creative intent. Fail the gate, notify via a reviewed OpenClaw skill, and route only SKUs explicitly marked “lossy allowed” through a separate documented job.
Identify reports srgba—does that fail rgba substring checks?
Normalize in bash: test for rgba or allowlist srgba when your ICC policy treats them equivalently; write the rule in YAML so QA and engineering read the same string.
Why do night builds fail only on the remote Mac?
Sleep assertions, unmounted external volumes, or a different PATH than interactive SSH. Keep caffeinate or power policy explicit, mount disks in the same plist, and log which magick on startup.
Should atlases that are intentionally non-POT ever pass?
Yes—when the brief explicitly waives POT for a subset. Encode require_pot: false per manifest row and branch the is_pot checks; never rely on “everyone remembers the exception.”
Summary: atomic .tmp → .png exports, debounced watch with single-flight locks, magick identify plus stat gates, reason-coded quarantine, JSONL gzip archives, and Gateway paths pinned to the job tree. Dedicated hosts: rent or buy, nodes and pricing, SSH and VNC—no login wall. More playbooks in Tech Insights.
Run SpriteAtlas PNG inspection on a dedicated remote Mac
Keep Unity export QA off artist laptops, pin OpenClaw and ImageMagick versions on Apple Silicon, and share threshold tables plus JSONL runbooks across studios.