# NumZoo — Agent Guidelines ## What this project is Kids mental math app. Correct answers earn AI-generated cute animal images (FLUX.2-klein-4B). Stack: Python · Gradio · diffusers · HuggingFace Spaces (ZeroGPU). ## Mandatory dev loop Run these scripts in order before every push. Fix failures before proceeding. | Step | Command | Must pass | |------|---------|-----------| | Syntax | `bash scripts/syntax.sh` | No syntax errors | | Imports | `bash scripts/check_imports.sh` | No import errors | | SDK version | `bash scripts/check_sdk.sh` | README sdk_version == local gradio | | Run | `bash scripts/run.sh` | App starts, no errors in logs | | Test | `bash scripts/test.sh` | All Puppeteer checks pass, screenshots look correct | Full loop shortcut: `bash scripts/dev.sh` (runs steps 1–4, then run test.sh manually). **Never push if any step fails.** ## Key files | File | Role | |------|------| | `app.py` | Gradio UI, game logic, panel visibility, event wiring | | `image_generator.py` | FLUX.2-klein-4B pipeline, persistent cache setup, emoji→text prompt builder | | `math_engine.py` | Question generation, level names & thresholds | | `requirements.txt` | Python dependencies — never include `gradio` here | | `README.md` | HF Spaces config (frontmatter `sdk_version`) + user docs | | `scripts/` | Dev loop automation scripts | ## Architecture **3-panel flow:** Welcome → Emoji Picker → Quiz **Game state dict keys:** `name`, `level`, `score`, `streak`, `correct_since_reward`, `level_correct`, `question`, `answer`, `selected_animals`, `selected_places`, `reward_count`, `pregenerate_next`, `generate_now`, `pending_reward_id` **Reward trigger:** every `REWARD_EVERY = 3` correct answers. **Pre-generation flow (async image UX):** 1. "Let's go!" → `start_game()` → adds locked placeholder for reward #1 in collection → `.then(pregenerate_image)` starts background generation immediately 2. `pregenerate_image` stores PIL image in `hidden_image` (gr.Image) and base64 data URL in `hidden_data_url` (gr.Textbox), sets `state["pending_reward_id"]` 3. When user earns a reward: `check_answer` receives `hidden_image` + `hidden_data_url` as inputs. - If pre-image matches reward_id → show immediately + unlock in collection + kick off next pregenerate - If not ready → show LOADER_HTML + set `generate_now=True` 4. `.then(generate_on_demand)` → generates on-demand if `generate_now`, unlocks collection 5. `.then(pregenerate_image)` → generates next reward in background **Collection system:** - `collection_trigger = gr.HTML("")` receives JSON-encoded action payloads - JS polls `#numzoo-coll-data` element every 200ms for `{ts, actions:[{action,id,src}]}` - Actions: `add-locked` (locked placeholder), `unlock` (reveal image), `failed` (remove) - Images stored in localStorage as base64 JPEGs (key: `numzoo_collection_v2`) **Loader fix:** Use value-swapping (`""` = hidden, `LOADER_HTML` = shown) instead of `visible` flag. This is more reliable in Gradio 6 than toggling visibility on gr.HTML components. ## Gradio rules - Use `gr.update(visible=True/False)` for visibility — never return bare booleans - Theme and CSS go in `demo.launch(css=..., theme=...)`, NOT in `gr.Blocks()` - `gr.Group` has no `.change()` event - Never put `gradio` in `requirements.txt` — HF Spaces pins it via `sdk_version` in README - Never put `audioop-lts` in `requirements.txt` — HF Spaces runs Python 3.10 where `audioop` is stdlib; `audioop-lts` requires >=3.13 and will break the build - Keep `sdk_version` in README in sync with local gradio (run `scripts/check_sdk.sh`) ## Python runtime - **Local:** `~/miniforge3/bin/python3` (arm64, Python 3.13, torch 2.12, MPS) - **HF Spaces:** Python 3.13, ZeroGPU (A100 via `@spaces.GPU`) - Do not use system Python (`/usr/local/bin/python3`) — it's x64 Rosetta, incompatible ## Image generation - Model: `black-forest-labs/FLUX.2-klein-4B` (Apache 2.0 — accept license on HF before first run) - Same model local + HF Spaces. 4 steps, `guidance_scale=1.0`, 512×512 - Local: float16 on MPS (needs ~13GB, fits in 32GB unified mem) · HF ZeroGPU: ~fast on A100 - Pipeline class: `Flux2KleinPipeline` (diffusers ≥ 0.38) - Always wrap in try/except → return `(None, "")` on failure - `IS_HF_SPACE = os.environ.get("SPACE_ID") is not None` controls ZeroGPU decorator - Logs generation time (`✅ Generated in Xs`) ## Persistent model cache (HF Spaces) - `/data/hf_cache_v4` mount caches the model across restarts (set in Space Settings → Storage) - Setup is **bulletproof**: poisoned/partial cache is auto-wiped & re-downloaded; any `/data` failure falls back to ephemeral container cache; import never crashes - If the cache tree gets poisoned again (`[Errno 20] Not a directory`), bump the path suffix (`_v4` → `_v5`) to escape it — established repo pattern - `NUMZOO_STYLE` constant in `image_generator.py` is the single source of truth for the art style; `scripts/generate_dataset.py` imports it so training captions match ## HuggingFace deployment - Space: `https://huggingface.co/spaces/Goumsss/numzoo` - Push = redeploy. Rebuilds automatically. - ZeroGPU: set in Space Settings → Hardware (requires HF Pro or hackathon grant) ## UI/UX principles - English only, minimal text — words not sentences (kids with limited English) - Emoji-heavy feedback (`✅ Great! 🔥🔥` not full sentences) - Never crash visibly — all handlers wrapped in try/except with safe fallbacks