# Project export: MOGGIE

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: QNX-powered, computer-vision-driven viral arcade system. Compete in instant 1v1 battles from online trends like six-seven and mogging. Portable and optimized gaming on Raspberry Pi.
- Devpost: https://devpost.com/software/moggie
- GitHub: https://github.com/BeanyZoldyck/moggie
- Video: https://www.youtube.com/embed/YuO-1lJqAFQ?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Best Use of QNX; Most Creative Use of Pika)
- Team: 4 GitHub contributor(s) — p1an0guy (60 commits), BeanyZoldyck (18 commits), emiliavu (16 commits), Mason Lewis (3 commits)

## Devpost submission (written by the team)

### Inspiration

We've seen many recent trends, like looksmaxxing, and have realized that they often encourage harmful behavior. We believe the solution is to foster open conversation and break down the stigma surrounding these topics, thereby promoting healthy decisions. When people can get together and feel comfortable sharing their struggles, they can find support and encouragement from those they value and whose opinions matter. What better way to break the ice than with a party game?

### What it does

Moggie is a portable AI party-game kiosk running on a Raspberry Pi 5 with QNX. Players stand in front of the camera and compete in fast physical and facial-expression mini-games, including hand-motion challenges, face mirroring, and emoji expression matching. The games can be understood instantly, leading to quick, accessible, and fun viral gameplay. Moggie lets people bond over the latest trends and socialize rather than isolate, helping address a major factor in the mental health issues and stigma associated with these internet phenomena. On a more technical level, we are running pygame on a Raspberry Pi 5, very generously provided by QNX, along with their real-time, microkernel-architecture operating system designed to handle time-sensitive applications. We wrote a custom camera driver from scratch to enable the provided camera to connect with our pygame pipeline. Each frame is passed through MediaPipe to produce body landmarks and geometry detection. Results are then timestamped and polled by the game frame by frame. A SQLite database stores per-game sessions and keeps track of the top scores in a leaderboard. Moggie is made to be shareable. Innately, it leads to viral shareable clips as the games are trendy and instant. Similar to other viral games, players get a ready-to-share clip after a MOGGIE session. Hit a high score or make a smart play? Pika + MOGGIE backend has a clip ready for your social media.

### How we built it

We brainstormed extensively as a team, then drafted up a design document with LLM tooling. We mainly used Codex 5.5 for most of the code implementation while focusing on what human brains still do best: ideation and creativity. We used our complimentary Claude credits to build the most complicated part of our application: the camera driver. We spent our time making UI/UX design choices, identifying ways to solve problems we encountered during implementation, and ensuring our project remained cohesive as multiple developers worked in parallel. The QNX camera path was the deepest systems work. We configured the QNX Sensor Framework for the Raspberry Pi Camera Module 3 / IMX708 stack, then wrote moggi_camgrab, a small C program that uses CamAPI’s read-only viewfinder path to receive NV12 frames. The grabber emits frames through a custom lightweight MGF1 wire format. A Python backend reads the stream on a background thread, keeps only the newest frame to avoid latency buildup, converts NV12 to BGR with OpenCV, and hands frames to the Pygame game screens. We also optimized the game around the Pi’s constraints. For example, we disable face tracking during the hand-motion game, use a faster, lightweight hand-motion fallback where needed, decimate camera frames in native code, and keep scoring/rendering independent from slower CV updates.

### Challenges we ran into

One of the main goals of our product is to keep it as lightweight and performant as possible; thus, we opted to test out the QNX operating system. However, because there was no existing camera functionality, we spent much of our time programming a C driver to enable our game to capture frames from the attached camera module. Additionally, the performance on a Pi is obviously not as powerful as on a MacBook Pro, but, because of our commitment to portability and affordability, we continuously iterated and optimized, like removing unnecessary face-tracking during a hand-movement-oriented game, to ensure that it still ran on the Pi.

### Accomplishments we're proud of

We got a real Raspberry Pi Camera Module 3 feed running inside a Pygame game on QNX, a use case in uncharted territory for QNX. That required: Configuring the QNX Sensor Framework for the Pi 5 camera path Building a native CamAPI frame grabber in C Designing a tiny custom NV12 stream protocol Bridging native camera frames into Python/OpenCV/Pygame Running local hand and face CV on constrained hardware Tuning the game so rendering and scoring stay responsive even when CV is slower We are especially proud that Moggie is not just a web mockup. It is a real embedded kiosk running on sponsor hardware with a live camera, HDMI display, local CV, and persistent gameplay state. We are able to play CV games at near-60 fps! Also, Moggie auto-clips and generates fun UGC-ready videos using Pika to increase virality. Hit a clip in Moggie and share with your friends!

### What we learned

By focusing on optimization, we can run compelling applications even on simple hardware. In an age when the answer to every problem seems to be throwing more compute or RAM at it, we proved that it's still possible to craft well-designed, performant software that maximizes hardware utilization and the onboard OS.

### What's next

for moggie We plan on expanding moggie to include more games. Eventually, we plan on creating a custom container and peripherals to actually sell as a party game! You can take Moggie to your friend's house, fun parties, mixers, and more! It attracts attention instantly and creates fun shareable moments.

## README (from the GitHub repository)

# Moggie

Moggie is a native Python kiosk game for Raspberry Pi OS 64-bit Lite. It is designed to boot directly into a Pygame/SDL2 shell, use one USB webcam for local CV gameplay, persist scores in SQLite, and treat cloud AI integrations as optional asynchronous polish.

## Local Development

Requirements:

- Python 3.10+
- A virtual environment
- Optional: Redis for leaderboard cache development
- Optional: webcam for camera smoke tests

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
cp .env.example .env
python -m app.main
```

In development mode, `python -m app.main` opens the windowed native shell. Use `python -m app.main --frames 3` for a short startup smoke test that exits cleanly.

## Demo Fallback Presets

Preset files live in `config_presets/` and can be used directly on a laptop or copied into `/etc/moggie/moggie.env` for the Pi systemd service.

```bash
./scripts/run_with_preset.sh cloud_disabled -- --frames 3
./scripts/run_with_preset.sh booth_safe
./scripts/run_with_preset.sh sixty_seven_solo
./scripts/run_with_preset.sh emoji_alternating
./scripts/run_with_preset.sh emoji_solo
```

- `cloud_disabled`: turns off Redis, Midjourney, Pika/Fal, LLM labels, cloud expression validation, and generated media saving. Local camera gameplay and SQLite leaderboards still work.
- `booth_safe`: production fullscreen plus the cloud-disabled path and conservative 640x480 camera / 320x240 CV settings.
- `sixty_seven_solo`: reduces 67 Challenge to one player and two tracked hands.
- `emoji_alternating`: keeps Emoji Face Match as a two-player flow but detects one face at a time.
- `emoji_solo`: last-resort single-player Emoji Face Match.

Attract mode is enabled by default. After `MOGGIE_IDLE_TIMEOUT_SECONDS` on the home screen, the kiosk cycles game cards, top scores, and recent generated media when `MOGGIE_SAVE_GENERATED_MEDIA=true`.

## Useful Scripts

```bash
./scripts/dev.sh
./scripts/init_db.sh
./scripts/smoke_test_camera.py
./scripts/auth_midjourney.py
./scripts/run_game.sh
./scripts/run_with_preset.sh booth_safe
```

## Midjourney Setup

Mog Mirror can use Midjourney's MCP server for async caricatures when `MOGGIE_ENABLE_IMAGE_GENERATION=true` and `MOGGIE_ENABLE_MIDJOURNEY=true`. Run `scripts/auth_midjourney.py` during setup over SSH or an admin terminal, complete OAuth on a phone or laptop, and paste the final callback URL back into the script.

The script writes token state to `MOGGIE_MIDJOURNEY_TOKEN_STORE`, defaulting to `~/.config/moggie/midjourney_oauth.json`, with `0600` permissions. The kiosk service later uses that token store without opening a browser. Missing or expired auth only fails the AI job; the local score reveal still completes.

## Raspberry Pi Launch Notes

The primary target is Raspberry Pi OS 64-bit Lite with no desktop environment. The service unit in `systemd/moggie.service` launches:

```bash
python -m app.main
```

Install system dependencies with `scripts/install_pi_lite_deps.sh`, then install the service with `scripts/install_systemd_service.sh`. The installer renders `/etc/systemd/system/moggie.service` with the current clone path, the current virtualenv Python, and the invoking user/group.

Recommended Pi setup:

```bash
sudo apt-get update
sudo apt-get install -y git
sudo mkdir -p /opt
sudo chown "$USER":"$USER" /opt
git clone git@github.com:BeanyZoldyck/moggie.git /opt/moggie
cd /opt/moggie
./scripts/install_pi_lite_deps.sh
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
./scripts/init_db.sh
./scripts/smoke_test_camera.py
./scripts/install_systemd_service.sh
sudo systemctl start moggie.service
```

Systemd reads `/etc/moggie/moggie.env` if present. The installer creates it from `config_presets/booth_safe.env` on first install. To switch demo mode:

```bash
sudo cp config_presets/cloud_disabled.env /etc/moggie/moggie.env
sudo systemctl restart moggie.service
```

Check service status and logs:

```bash
sudo systemctl status moggie.service
journalctl -u moggie.service -f
```

## Camera Troubleshooting

The kiosk retries a missing camera every `MOGGIE_CAMERA_RETRY_SECONDS` and shows the diagnostic in the home preview. If a camera opens and then drops frames, Moggie keeps the last good frame and continues polling.

Use these checks before judging:

```bash
v4l2-ctl --list-devices
MOGGIE_CAMERA_INDEX=0 ./scripts/smoke_test_camera.py
MOGGIE_CAMERA_INDEX=1 ./scripts/smoke_test_camera.py
```

If the preview is blank, unplug/replug the webcam, rerun the smoke test with the discovered index, update `/etc/moggie/moggie.env`, and restart the service.

### QNX Raspberry Pi Camera Module 3

On the QNX Raspberry Pi 5 image, use the QNX CamAPI backend instead of `cv2.VideoCapture`:

```bash
gcc native/moggi_camgrab.c -o moggi_camgrab -lcamapi
./moggi_camgrab
MOGGIE_CAMERA_BACKEND=qnx ./scripts/smoke_test_camera.py
MOGGIE_CAMERA_BACKEND=qnx python -m app.main
```

The helper streams NV12 frames from the CamAPI viewfinder into Python. Pygame display still needs to run on the Pi HDMI console.

## Judging Day Runbook

1. Boot the Pi and confirm the idle attract screen appears after the home screen sits idle.
2. Run one Mog Mirror round and confirm scores land on the leaderboard.
3. Run 10 consecutive 67 Challenge rounds from home to score reveal and back. If tracking drops or FPS is poor, copy `config_presets/sixty_seven_solo.env` into `/etc/moggie/moggie.env` and restart.
4. Run Emoji Face Match. If two-face scoring is unreliable, switch to `emoji_alternating.env`; if that still fails, switch to `emoji_solo.env`.
5. If venue network or cloud auth is unstable, switch to `cloud_disabled.env` or `booth_safe.env`. The local demo path should still play and persist scores.
6. Keep `journalctl -u moggie.service -f` open on an SSH session during setup. During judging, leave the kiosk fullscreen.

Demo reset:

```bash
sudo systemctl stop moggie.service
rm -f data/moggie.sqlite
./scripts/init_db.sh
sudo systemctl start moggie.service
```

Generated media reset, when media saving has been enabled:

```bash
rm -rf media/*
```

Stress-test checklist:

- 10 back-to-back 67 Challenge rounds complete without a crash.
- 10 home -> game -> score reveal -> leaderboard -> home transitions complete without a crash.
- Camera unplug/replug shows a diagnostic, then recovers after restart or retry.
- Cloud-disabled preset completes all local games with Redis and cloud services unavailable.
- Text remains readable on 1080p HDMI from booth distance.

## Project Layout

- `app/main.py`: application entrypoint
- `app/core/moggie_app.py`: Pygame app loop
- `app/config.py`: typed environment configuration
- `app/db.py`: SQLite initialization
- `app/core/`: event bus, game registry, session/screen managers
- `app/services/`: camera, CV, leaderboard, storage, Redis, AI, sponsor integration boundaries
- `app/games/`: game modules
- `app/cv/`: local CV helpers and heuristics
- `app/ai/`: cloud/mock AI client interfaces
- `scripts/`: local, Pi, systemd, DB, and camera utilities
- `tests/`: foundation tests


## Detected evidence (automated analysis)

Indexed codebase: 166 recognized source files, 1543 KB.
- C (language) — detected in the code
- HTML (language) — detected in the code
- Python (language) — detected in the code
- Redis (technology) — detected in the code

## Codebase structure (from repository index)

### Files (120 of 185)

```
.agents/skills/app-sizzle/references/liquid-glass.md
.agents/skills/app-sizzle/references/template-a-cinematic.md
.agents/skills/app-sizzle/SKILL.md
.agents/skills/app-store-screens/references/default-layout.md
.agents/skills/app-store-screens/references/layout-archetypes.md
.agents/skills/app-store-screens/references/render-pipeline.md
.agents/skills/app-store-screens/SKILL.md
.agents/skills/baseball-trend/SKILL.md
.agents/skills/build-a-brand/references/brand-directions.md
.agents/skills/build-a-brand/references/brand-guidelines.md
.agents/skills/build-a-brand/references/brand-identity.md
.agents/skills/build-a-brand/references/brand-md-template.md
.agents/skills/build-a-brand/SKILL.md
.agents/skills/content-director/formats/dance.md
.agents/skills/content-director/formats/duet.md
.agents/skills/content-director/formats/pov.md
.agents/skills/content-director/formats/talking.md
.agents/skills/content-director/formats/teleprompter.html
.agents/skills/content-director/formats/teleprompter.md
.agents/skills/content-director/README.md
.agents/skills/content-director/SKILL.md
.agents/skills/explainer/SKILL.md
.agents/skills/fix-my-look/SKILL.md
.agents/skills/founder-product-video/references/ops-notes.md
.agents/skills/founder-product-video/SKILL.md
.agents/skills/kiss-cam/SKILL.md
.agents/skills/language-swap/references/language-coverage.md
.agents/skills/language-swap/SKILL.md
.agents/skills/persona-builder/references/aesthetic-prompts.md
.agents/skills/persona-builder/references/persona-md-template.md
.agents/skills/persona-builder/references/templates/mood-board-no-header.template.html
.agents/skills/persona-builder/references/templates/mood-board.template.html
.agents/skills/persona-builder/references/templates/pdf-about.template.html
.agents/skills/persona-builder/references/templates/pdf-content-categories.template.html
.agents/skills/persona-builder/references/templates/pdf-cover.template.html
.agents/skills/persona-builder/references/templates/pdf-do-dont.template.html
.agents/skills/persona-builder/references/templates/pdf-hooks-dm.template.html
.agents/skills/persona-builder/references/templates/pdf-moodboard.template.html
.agents/skills/persona-builder/references/templates/pdf-next-steps.template.html
.agents/skills/persona-builder/references/templates/pdf-shared-head.template.html
.agents/skills/persona-builder/references/templates/pdf-voice-mode.template.html
.agents/skills/persona-builder/SKILL.md
.agents/skills/podcast/SKILL.md
.agents/skills/ugc-ads/SKILL.md
.agents/skills/viral-hook/SKILL.md
.env.example
.gitignore
app/__init__.py
app/ai/__init__.py
app/ai/base.py
app/ai/fal_pika_client.py
app/ai/image_generation_client.py
app/ai/midjourney_mcp_client.py
app/ai/mock_clients.py
app/ai/pika_mcp_client.py
app/ai/text_generation_client.py
app/config.py
app/core/__init__.py
app/core/app_event.py
app/core/event_bus.py
app/core/game_catalog.py
app/core/game_registry.py
app/core/game_session_manager.py
app/core/moggie_app.py
app/core/screen_manager.py
app/core/worker.py
app/cv/__init__.py
app/cv/expression_features.py
app/cv/face_detection.py
app/cv/hand_landmarks.py
app/cv/mediapipe_compat.py
app/cv/simple_hand_detection.py
app/cv/sixty_seven_counter.py
app/cv/zone_assignment.py
app/db.py
app/games/__init__.py
app/games/base.py
app/games/emoji_face_match.py
app/games/mog_mirror.py
app/games/recap_prompts.py
app/games/sixty_seven.py
app/games/voicelines.py
app/main.py
app/models/__init__.py
app/models/media_asset.py
app/models/player.py
app/models/score.py
app/models/session.py
app/services/__init__.py
app/services/ai_job_service.py
app/services/camera_service.py
app/services/cv_service.py
app/services/leaderboard_service.py
app/services/qnx_camera_backend.py
app/services/redis_cache_service.py
app/services/social_post_service.py
app/services/sponsor_integration_service.py
app/services/storage_service.py
app/services/voice_agent_audio.py
app/services/voice_agent_service.py
app/services/voice_mic_capture.py
app/services/voice_service.py
app/ui/__init__.py
app/ui/input.py
app/ui/layout.py
app/ui/render_utils.py
app/ui/renderers/__init__.py
app/ui/renderers/camera_preview_renderer.py
app/ui/renderers/face_overlay_renderer.py
app/ui/renderers/game_shell_renderer.py
app/ui/renderers/hand_overlay_renderer.py
app/ui/screens/__init__.py
app/ui/screens/calibration_screen.py
app/ui/screens/emoji_face_match_screen.py
app/ui/screens/home_screen.py
app/ui/screens/idle_attract_screen.py
app/ui/screens/leaderboard_screen.py
app/ui/screens/mog_mirror_screen.py
app/ui/screens/player_setup_screen.py
app/ui/screens/score_reveal_screen.py
[65 more files omitted for size]
```

### Dependencies

- pyproject.toml: boto3@>=1.34, mediapipe@>=0.10, opencv-python@>=4.9, pygame@>=2.5, pytest@>=8.0, redis@>=5.0, requests@>=2.32, requests-oauthlib@>=2.0, sounddevice@>=0.4, websocket-client@>=1.8

### Recent commits (newest first)

- Merge branch 'mainline' of github.com:BeanyZoldyck/moggie into mainline
- add mermaid diagram
- cacheing
- cacheing
- devpost visuals w pika
- Player name position fix
- Merge pull request #28 from BeanyZoldyck/ui-improvement
- added sparkles
- test voice agent
- tiwtter voice agent
- fixed mog mirror winner UI
- voice agent final
- added names back to ui
- ignore media files
- fix leaderboard UI
- voicelines
- deepgram tts
- pika fix
- redis fix
- leaderboard + storage

## Key source files (fetched from GitHub, selected and truncated for size)

### pika.md

```markdown
# Moggie — opt-in AI recap videos (fal.ai Pika v2.2 image-to-video)

All three games now offer an optional, post-game AI **recap video** — a dramatised highlight
clip of what just happened, generated from a camera frame taken at the end of the round,
with a result-aware prompt. Nothing is generated unless the player presses **G** on the
score-reveal screen, so no credits are spent by default.

---

## Games covered

| Game | Prompt style |
|------|-------------|
| **Mog Mirror** | Cinematic 1v1 face-off replay, names the winner and their aura score |
| **67 Challenge** | High-energy sports broadcast, names the winner and their rep count |
| **Emoji Face Match** | Fast meme-ready clip, names the winner and their points |

One video is generated per round (both players visible in the same frame, P1 left / P2 right).

---

## Setup

Config lives in `.env` (gitignored — never commit the key):

```bash
MOGGIE_ENABLE_PIKA=true                              # enables the G-key recap prompt on score reveal
MOGGIE_PIKA_PROVIDER=fal                             # fal.ai queue API
FAL_KEY=<your-fal-key>                               # format: <id>:<secret>
MOGGIE_PIKA_MODEL=fal-ai/pika/v2.2/image-to-video    # current model
MOGGIE_AI_TIMEOUT_SECONDS=120                         # image-to-video is slow; 45s default is too low
MOGGIE_AI_POLL_INTERVAL_SECONDS=3

# --- optional: save clips to disk ---
MOGGIE_SAVE_GENERATED_MEDIA=true          # copy each clip to MOGGIE_MEDIA_DIR
MOGGIE_MEDIA_DIR=./media                  # directory for saved clips (default: ./media)
```

Install dependencies:

```bash
python3 -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"
```

---

## How to use

1. Play any game to completion (Mog Mirror, 67 Challenge, Emoji Face Match).
2. On the **score-reveal screen**, a hint appears at the bottom: **`G — AI RECAP`**.
3. Press **G**. The screen dims with "GENERATING RECAP…" while fal.ai renders the clip
   (typically 30–90 seconds).
4. When ready the clip loops over the results. **ESC / ENTER** returns home.

If generation fails or the user doesn't press G, nothing is charged and the round ends normally.

---

## Retrieving the video

### Option A — copy the URL from the log

The generated video URL is logged when the clip arrives:

```
INFO  [app.ui.screens.score_reveal_screen] Recap: generated video https://v3.fal.media/files/...
INFO  [app.ui.screens.score_reveal_screen] Recap: ready — tmp=/tmp/moggie_avatar_xxx.mp4 ...
```

Copy the fal.media URL and open it in a browser or `curl` it down.

### Option B — auto-save to disk (recommended for sharing)

Set `MOGGIE_SAVE_GENERATED_MEDIA=true` in `.env`. After each replay is downloaded,
it is copied to `MOGGIE_MEDIA_DIR` with a name like:

```
./media/mog_mirror_recap_moggie_avatar_abc123.mp4
./media/sixty_seven_recap_moggie_avatar_def456.mp4
```

Files persist between sessions. The path is also logged:

```
INFO  [app.ui.screens.score_reveal_screen] Recap saved to media/mog_mirror_r
[truncated — 4203 more characters]
```

### QNX_PI5_RECOVERY_SETUP.md

```markdown
# Moggie QNX Pi 5 Recovery Setup

Use this if the current Raspberry Pi 5 dies and we have a fresh same-model Pi 5 with QNX installed. This runbook assumes SSH to the new Pi already works as `qnxuser` and the password is `qnxuser`.

The goal is to get the current Moggie game running with Raspberry Pi Camera Module 3 over QNX CamAPI, HDMI, and Pygame.

## 0. Source Of Truth

Important local files on the laptop:

- `/home/mason/Moggie/actual-game` - current game source
- `/home/mason/Moggie/HANDOFF_QNX_CAMERA.md` - detailed camera investigation and fix
- `/home/mason/Moggie/actual-game/native/moggi_camgrab.c` - native QNX CamAPI grabber
- `/home/mason/Moggie/actual-game/app/services/qnx_camera_backend.py` - Python camera backend
- `/home/mason/Moggie/actual-game/run_qnx_hdmi.sh` - kiosk launch script
- `/home/mason/Moggie/actual-game/config_presets/qnx_camera.env` - QNX camera env defaults

Before relying on GitHub alone, check for uncommitted local changes:

```sh
cd /home/mason/Moggie/actual-game
git status --short --branch
```

If there are local changes, copy the local folder to the Pi instead of only cloning from GitHub.

## 1. Confirm Basic QNX Access

From the laptop or WSL helper, confirm:

```sh
ssh qnxuser@qnxpi32.local 'uname -a; id; pwd'
```

From WSL, direct SSH may not route to the Pi. Use Windows `ssh.exe` or recreate the helper from `HANDOFF_QNX_CAMERA.md`:

```sh
/home/mason/qx.sh 'uname -a; id; pwd'
```

If the Pi IP changed, discover it from Windows:

```powershell
Resolve-DnsName qnxpi32.local
```

Then run with:

```sh
QNX_HOST=192.168.137.X /home/mason/qx.sh 'uname -a'
```

## 2. Install QNX Camera Packages

Run on the Pi:

```sh
echo qnxuser | sudo -S apk add \
  yaml \
  libturbojpeg \
  qnx-egl \
  qnx-gles \
  qnx-screen \
  qnx-devu-hcd-dwc3-xhci \
  qnx-sensor-framework-rpi-camera-ipa \
  qnx-io-usb-otg \
  qnx-usb \
  qnx-multimedia-framework \
  qnx-sensor-framework \
  qnx-sensor-framework-camera-imx708 \
  qnx-sensor-framework-rpi5 \
  qnx-sensor-framework-utils
```

Do not hand-copy random `libcamapi` or `libsensor` files. The camera previously broke because the sensor service and libraries were version-mismatched. Use `apk add` so the package set stays consistent.

## 3. Persist The Camera Service Startup

Edit the real startup file:

```sh
echo qnxuser | sudo -S cp /usr/etc/startup/post_startup.sh /usr/etc/startup/post_startup.sh.bak-moggi-recovery
echo qnxuser | sudo -S vi /usr/etc/startup/post_startup.sh
```

The active camera sensor line must be:

```sh
sensor -U 521:521 -b external -r /data/share/sensor -c /system/etc/config/sensor/rpi5_camera_module3.conf
```

Comment out any old generic sensor line that uses `sensor_demo.conf`.

Reboot after editing:

```sh
echo qnxuser | sudo -S shutdown
```

After reboot, verify camera nodes:

```sh
ls /dev/sensor
```

Expected:

```text
camera1
data1
data2
```

If missing, manually restart the service:

```sh
echo qnxuser | sudo -S slay -f sensor
sleep 2
echo qnxuser | sud
[truncated — 4853 more characters]
```

### pyproject.toml

```
[build-system]
requires = ["setuptools>=69", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "moggie"
version = "0.1.0"
description = "Native Python Raspberry Pi kiosk party game."
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
    "pygame>=2.5",
    "opencv-python>=4.9",
    "mediapipe>=0.10",
    "redis>=5.0",
    "boto3>=1.34",
    "requests>=2.32",
    "requests-oauthlib>=2.0",
    "websocket-client>=1.8",
    "sounddevice>=0.4",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
]

[tool.setuptools]
packages = {find = {include = ["app*"]}}

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]

```

### app/main.py

```python
from __future__ import annotations

import argparse
import logging

from app.config import ConfigError, MoggieConfig, load_config
from app.core.moggie_app import MoggieApp
from app.db import initialize_database
from app.util.logging import setup_logging

LOGGER = logging.getLogger(__name__)


class RuntimeDependencyError(RuntimeError):
    """Raised when a required runtime package is missing."""


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Start the Moggie kiosk application.")
    parser.add_argument(
        "--frames",
        type=int,
        default=None,
        help="Override app frame count for smoke tests. Use 0 to run until interrupted.",
    )
    return parser


def run_app(config: MoggieConfig, frames: int | None = None) -> None:
    try:
        MoggieApp(config).run(frames=frames)
    except ModuleNotFoundError as exc:
        if exc.name == "pygame":
            raise RuntimeDependencyError(
                "pygame is not installed; run `python3 -m pip install -e \".[dev]\"` "
                "inside the project environment."
            ) from exc
        raise


def main() -> int:
    args = build_parser().parse_args()
    try:
        config = load_config()
    except ConfigError as exc:
        setup_logging("development")
        LOGGER.error("Invalid Moggie configuration: %s", exc)
        return 2

    setup_logging(config.env)
    initialize_database(config.db_path)
    try:
        run_app(config, frames=args.frames)
    except RuntimeDependencyError as exc:
        LOGGER.error("%s", exc)
        return 2
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

```

### qnx_login.sh

```shell
curl 'https://captiveportal-login.berkeley.edu/cgi-bin/login' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \
  -H 'Accept-Language: en-US,en;q=0.9' \
  -H 'Cache-Control: max-age=0' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -b 'CPsession=http://connectivitycheck.gstatic.com/generate_204' \
  -H 'Origin: https://welcome.wifi.berkeley.edu' \
  -H 'Referer: https://welcome.wifi.berkeley.edu/' \
  -H 'Sec-Fetch-Dest: document' \
  -H 'Sec-Fetch-Mode: navigate' \
  -H 'Sec-Fetch-Site: same-site' \
  -H 'Upgrade-Insecure-Requests: 1' \
  -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36' \
  -H 'sec-ch-ua: "Chromium";v="148", "Google Chrome";v="148", "Not/A)Brand";v="99"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "Linux"' \
  --data-raw 'user=berkeley-visitor&password=76%21kzLOA&cmd=authenticate&url=http%3A%2F%2Ffreshbeautifulwonderousspell.neverssl.com%2Fonline&Login=Log+In'

```

### run_qnx_hdmi.sh

```shell
#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")"

if [ -f "../.venv/bin/activate" ]; then
  # Deployed QNX layout: ~/moggi-qnx/actual-game with venv at ~/moggi-qnx/.venv.
  # shellcheck disable=SC1091
  source "../.venv/bin/activate"
elif [ -f ".venv/bin/activate" ]; then
  # Local/repo layout.
  # shellcheck disable=SC1091
  source ".venv/bin/activate"
fi

export MOGGIE_CAMERA_BACKEND="${MOGGIE_CAMERA_BACKEND:-qnx}"
export MOGGIE_TARGET_FPS="${MOGGIE_TARGET_FPS:-60}"
export MOGGIE_QNX_CAMERA_GRABBER="${MOGGIE_QNX_CAMERA_GRABBER:-./moggi_camgrab}"
export MOGGIE_QNX_CAMERA_UNIT="${MOGGIE_QNX_CAMERA_UNIT:-1}"
export MOGGIE_QNX_CAMERA_DECIMATE="${MOGGIE_QNX_CAMERA_DECIMATE:-3}"
export MOGGIE_CAMERA_GAIN="${MOGGIE_CAMERA_GAIN:-2.25}"
export MOGGIE_CAMERA_BRIGHTNESS="${MOGGIE_CAMERA_BRIGHTNESS:-42}"
export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION="${PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION:-python}"
export MOGGIE_HAND_TRACKING_BACKEND="${MOGGIE_HAND_TRACKING_BACKEND:-mediapipe}"
export MOGGIE_CV_ENABLE_FACE_DETECTION="${MOGGIE_CV_ENABLE_FACE_DETECTION:-true}"
export MOGGIE_FACE_TRACKING_BACKEND="${MOGGIE_FACE_TRACKING_BACKEND:-mediapipe}"
export MOGGIE_67_MIN_CONFIDENCE="${MOGGIE_67_MIN_CONFIDENCE:-0.25}"

export MOGGIE_ENABLE_REDIS_LEADERBOARD_CACHE="${MOGGIE_ENABLE_REDIS_LEADERBOARD_CACHE:-false}"
export MOGGIE_ENABLE_IMAGE_GENERATION="${MOGGIE_ENABLE_IMAGE_GENERATION:-false}"
export MOGGIE_ENABLE_MIDJOURNEY="${MOGGIE_ENABLE_MIDJOURNEY:-false}"
export MOGGIE_ENABLE_PIKA="${MOGGIE_ENABLE_PIKA:-false}"
export MOGGIE_ENABLE_LLM_LABELS="${MOGGIE_ENABLE_LLM_LABELS:-false}"
export MOGGIE_EMOJI_USE_CLOUD_VALIDATION="${MOGGIE_EMOJI_USE_CLOUD_VALIDATION:-false}"

python -m app.main "$@"

```

### scripts/init_db.sh

```shell
#!/usr/bin/env bash
set -euo pipefail

PYTHON="${PYTHON:-python3}"

"$PYTHON" -m app.db

```

### app/__init__.py

```python
"""Moggie kiosk application package."""

__all__ = ["__version__"]

__version__ = "0.1.0"

```

### scripts/run_game.sh

```shell
#!/usr/bin/env bash
set -euo pipefail

export MOGGIE_ENV="${MOGGIE_ENV:-production}"
export MOGGIE_PLACEHOLDER_FRAMES="${MOGGIE_PLACEHOLDER_FRAMES:-0}"
PYTHON="${PYTHON:-python3}"

"$PYTHON" -m app.main

```

### scripts/dev.sh

```shell
#!/usr/bin/env bash
set -euo pipefail

export MOGGIE_ENV="${MOGGIE_ENV:-development}"
export MOGGIE_PLACEHOLDER_FRAMES="${MOGGIE_PLACEHOLDER_FRAMES:-0}"
PYTHON="${PYTHON:-python3}"

"$PYTHON" -m app.main

```

[148 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]