diff --git a/.gitea/workflows/README.md b/.gitea/workflows/README.md
new file mode 100644
index 0000000..4099b71
--- /dev/null
+++ b/.gitea/workflows/README.md
@@ -0,0 +1,51 @@
+# Gitea Actions CI
+
+Workflow defined in [`build.yml`](./build.yml). Triggered on push / PR to
+`dev` and `main`, or manually via `workflow_dispatch`.
+
+## Jobs
+
+| Job | Image | Role |
+|----------------------|-----------------------------|------------------------------------------------------------------------------------------|
+| `validate` | `barichello/godot-ci:4.6` | `godot --headless --import` then grep for `SCRIPT ERROR` / `Parse Error`. Uploads `.godot/` cache. |
+| `export-desktop` | `barichello/godot-ci:4.6` | Matrix: Windows / Linux / macOS. Reuses the import cache, uploads each binary as artifact. |
+| `export-android` | `barichello/godot-ci:4.6` + JDK 17 + Android SDK installed at runtime | Provisions keystore, writes `editor_settings-4.tres` with SDK / JDK paths, exports APK. |
+
+Artifacts are kept 14 days, accessible from the Gitea run page.
+
+## Prerequisites before the first successful run
+
+1. **Docker image tag** — verify `barichello/godot-ci:4.6` exists on Docker
+ Hub. Otherwise adjust `GODOT_IMAGE` (common alternatives: `4.3`, `4.4`).
+2. **macOS preset missing** — add it in Godot Editor → Project → Export →
+ Add → macOS, name it exactly `macOS` (or change the matrix entry). The
+ `.zip` will be unsigned; on Mac it needs
+ `xattr -dr com.apple.quarantine` to launch.
+3. **`Linux/X11Debug` preset** — Godot-3-era name. Reopen the project in
+ Godot 4 once and re-save the preset (the editor may rename it). Update
+ the matrix `preset:` field accordingly if it does.
+4. **Gitea runner** — must support Docker containers (`act_runner` in
+ `docker` mode, or `host` mode with Docker installed). Check
+ `act_runner exec` access to Docker Hub.
+5. **Optional secret** `ANDROID_KEYSTORE_BASE64` — `base64 -w0 debug.keystore`,
+ stored as a Gitea repo secret. Without it, a throwaway keystore is
+ generated per run, so the APK signature changes every build.
+
+## Differences from the old `.drone.yml`
+
+- No more Drone, no more Butler — build only, artifacts downloadable from
+ the Gitea UI.
+- GDScript validation step before export (didn't exist).
+- `.godot/` import cache shared between jobs (faster reruns).
+- Keystore via Gitea secret instead of a public pCloud link.
+- macOS target added (preset still to be created in Godot).
+- `master` / empty `ReleaseVersion` pipeline → replaced by triggers on
+ `main` (release branch per `CLAUDE.md`).
+
+## Future: itch.io deploy via Butler
+
+Not wired. When you want it back, add a `deploy-itch` job gated on tag
+push (`v*`) that downloads the artifacts and runs
+`butler push
dev-crea/ahog:` with `BUTLER_API_KEY` from
+secrets. Channels used historically:
+`windows`, `linux`, `android`, `mac`.
diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
new file mode 100644
index 0000000..595406c
--- /dev/null
+++ b/.gitea/workflows/build.yml
@@ -0,0 +1,171 @@
+name: Build Puzzle Quest
+
+on:
+ push:
+ branches: [dev, main]
+ pull_request:
+ branches: [dev, main]
+ workflow_dispatch:
+
+env:
+ GODOT_VERSION: "4.6"
+ GODOT_IMAGE: "barichello/godot-ci:4.6"
+
+jobs:
+ # ---------------------------------------------------------------------------
+ # 1. GDScript validation — parse every script and fail on errors / warnings.
+ # ---------------------------------------------------------------------------
+ validate:
+ name: Validate GDScript
+ runs-on: ubuntu-latest
+ container:
+ image: barichello/godot-ci:4.6
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Import project (parses every .gd / .tscn)
+ run: |
+ godot --headless --import 2>&1 | tee /tmp/godot-import.log || true
+
+ - name: Fail on parse / script errors
+ run: |
+ if grep -qE "SCRIPT ERROR|Parse Error|ERROR: .*\.gd" /tmp/godot-import.log; then
+ echo "::error::GDScript errors detected during import"
+ grep -E "SCRIPT ERROR|Parse Error|ERROR: .*\.gd" /tmp/godot-import.log
+ exit 1
+ fi
+
+ - name: Upload import cache
+ uses: actions/upload-artifact@v3
+ with:
+ name: godot-import-cache
+ path: .godot
+ retention-days: 1
+
+ # ---------------------------------------------------------------------------
+ # 2. Desktop exports (Windows / Linux / macOS) — runs in parallel.
+ # macOS preset must be added in the Godot editor before this matrix entry
+ # can succeed (export_presets.cfg currently has none).
+ # ---------------------------------------------------------------------------
+ export-desktop:
+ name: Export ${{ matrix.platform }}
+ needs: validate
+ runs-on: ubuntu-latest
+ container:
+ image: barichello/godot-ci:4.6
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - platform: Windows
+ preset: WindowsDebug
+ output: releases/windows/Puzzle-Quest.exe
+ artifact_path: releases/windows
+ - platform: Linux
+ preset: Linux/X11Debug
+ output: releases/linux/Puzzle-Quest.x86_64
+ artifact_path: releases/linux
+ - platform: macOS
+ preset: macOS
+ output: releases/macos/Puzzle-Quest.zip
+ artifact_path: releases/macos
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Restore import cache
+ uses: actions/download-artifact@v3
+ with:
+ name: godot-import-cache
+ path: .godot
+
+ - name: Prepare output dir
+ run: mkdir -p "${{ matrix.artifact_path }}"
+
+ - name: Export ${{ matrix.platform }}
+ run: godot --headless --export-debug "${{ matrix.preset }}" "${{ matrix.output }}"
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: PuzzleQuest-${{ matrix.platform }}
+ path: ${{ matrix.artifact_path }}
+ if-no-files-found: error
+ retention-days: 14
+
+ # ---------------------------------------------------------------------------
+ # 3. Android export — needs JDK + Android SDK on top of the Godot image.
+ # Provide ANDROID_KEYSTORE_BASE64 as a Gitea secret for a stable signature;
+ # otherwise a fresh debug keystore is generated on each run.
+ # ---------------------------------------------------------------------------
+ export-android:
+ name: Export Android
+ needs: validate
+ runs-on: ubuntu-latest
+ container:
+ image: barichello/godot-ci:4.6
+ env:
+ ANDROID_HOME: /opt/android-sdk
+ JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install JDK 17 + unzip
+ run: |
+ apt-get update
+ apt-get install -y --no-install-recommends openjdk-17-jdk wget unzip
+
+ - name: Install Android command-line tools + SDK
+ run: |
+ mkdir -p "$ANDROID_HOME/cmdline-tools"
+ wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline.zip
+ unzip -q /tmp/cmdline.zip -d "$ANDROID_HOME/cmdline-tools"
+ mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest"
+ yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses >/dev/null
+ "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
+ "platform-tools" "platforms;android-34" "build-tools;34.0.0" >/dev/null
+
+ - name: Provision debug keystore
+ env:
+ ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
+ run: |
+ if [ -n "$ANDROID_KEYSTORE_BASE64" ]; then
+ echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > /root/debug.keystore
+ else
+ keytool -keyalg RSA -genkeypair -alias androiddebugkey -keypass android \
+ -keystore /root/debug.keystore -storepass android \
+ -dname "CN=Android Debug,O=Android,C=US" -validity 9999
+ fi
+ sed -i 's@keystore/debug=".*"@keystore/debug="/root/debug.keystore"@g' export_presets.cfg
+
+ - name: Write Godot editor settings (Android SDK / JDK paths)
+ run: |
+ mkdir -p ~/.config/godot
+ cat > ~/.config/godot/editor_settings-4.tres <